Sessions and signers
A session is one document being signed under one template, on one device. People join it as they arrive, each signs the fields assigned to them in the order the template sets, and each confirmation is recorded the moment it is given. The reviewer later reads, in the audit chain, who joined when, which field each person signed, and in what order.
A session that ends partially can be continued by a new session, on this device or another, which carries its signatures; see Finalizing.
See it running: Sign a document.
Setup
Add SignArgs → Local Finalizer next to your PdfWorker and set:
| Field | Value |
|---|---|
| Worker | the PdfWorker |
| Folder | the folder under Application.persistentDataPath bundles are written to; SignArgs by default |
| Audit font | empty for the packaged Roboto, or your own font, see The audit page and fonts |
| Image pixels per point | resolution of the stamped signature image; 4 by default, 1 to 8 |
Start
LocalSigningSession session = await finalizer.StartAsync(documentBytes, "Handover protocol, pump station 7", templateBytes);
The title is printed on the audit page. Starting checks everything before anyone sees the document, which is why starting can throw and confirming later cannot for the same reasons:
| Exception | Cause |
|---|---|
RecordFormatException |
The template is not a valid template record. |
ArgumentException |
The template is bound to another document by DocumentSha256. |
ArgumentOutOfRangeException |
A field is on a page the document does not have, or its area does not fit on its page. |
AuditTextException |
A text printed on the audit page is empty or cannot be printed: the title, the template’s name, a field label, a consent sentence, your application’s identifier or version, or the package name or version. |
AuditTextException derives from ArgumentException, so catch it first.
| PdfLoadException | The document cannot be opened. |
LocalFinalizer.CurrentApplication is what the session records about your application and the device:
Application.identifier (or Application.productName on players Unity builds without an
identifier, such as Windows), Application.version, the package name and version, and
SystemInfo.deviceModel. The audit page prints the first two, as reported by the application.
Add the signers
var signer = new SignerDetails(
"signer-1", // opaque reference: never a name or an email address
"Anna Müller", // printed on the audit page, kept out of the records
string.Empty, // email or phone, may be empty
AssuranceLevel.Presence, // how sure your application is of this person
new[] { "contractor" }); // the fields they sign
IdentityRecord identity = await finalizer.AddSignerAsync(session, signer);
Add each person when they arrive: the chain records when that happened. SignerRef is 1 to 64 of
A-Z a-z 0-9 . _ - and must not identify the person; it is what the records use in place of their
name. A signer is refused with an ArgumentException when their reference is already in the session,
their list of fields is empty, a field is unknown or already taken, or their assurance level is below
a field’s minimum, and with an AuditTextException when the
audit page cannot print their name.
Check names while they are typed, so nobody is refused after signing:
nameInput.onValueChanged.AddListener(text =>
startButton.interactable = AuditText.IsPrintable(text, finalizer.AuditFont));
Field states
session.State(fieldId) says what a field is waiting for:
| State | Meaning |
|---|---|
Unassigned |
No signer has been added for it. |
Blocked |
Its signer is here, but a field earlier in the signing order is not confirmed. |
Ready |
It can be confirmed. |
Confirmed |
It is signed. |
session.Template.Fields lists the fields in template order, session.Field(id) returns one, and
session.Signature(id) returns its signature record once confirmed.
Confirm
Enable the pad only once the field’s page is on screen; PdfView.PageShown tells you when that
happened. Whether the signer must page through the whole document or may jump to the page is your
application’s rule; the records state which pages were shown.
Confirming must be a deliberate act of the signer: a button they press or hold after reading the consent sentence. Never confirm when the pen lifts.
SignatureRecord record = await finalizer.ConfirmAsync(session, fieldId, pad, view);
pad.Clear();
ConfirmAsync takes the strokes from the pad, draws them into an image the size of the field at
image pixels per point, and records them together with the pages the view has shown, the
document’s hash, the template’s hash, the consent sentence and the evidence. The PDF is not changed
yet. It throws:
| Exception | Cause |
|---|---|
InvalidOperationException |
Nothing is drawn, no document is open in the view, the field has no signer, it is already confirmed, or the signing order puts another field first (the message names it). |
ArgumentException |
The field id is unknown, or the view never showed the field’s page. |
Show the taken signatures over the page yourself until finalizing stamps them, as the sample does, so page hashes stay hashes of what the engine rendered from the original.
Threads
LocalFinalizer runs engine and session work on the PdfWorker thread and returns to the main
thread; ConfirmAsync draws the signature image on the thread pool first. The audit font is loaded
on the main thread the first time AuditFont is read. A session
has one caller at a time: await each call before making the next one on the same session, and call
session.AddEvidence, State, Field or Signature only while no LocalFinalizer call on that
session is in flight. Calling LocalSigningSession.Start, AddSigner, Confirm or Finalize
yourself is for code that already runs off the main thread.
Next: Finalizing.