148 lines
3.8 KiB
Go
148 lines
3.8 KiB
Go
package pdfcert
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
//go:embed logo.png
|
|
var embeddedLogo []byte
|
|
|
|
// Input for the official Verae Timestamp Registration Document.
|
|
type Input struct {
|
|
DocumentName string
|
|
SHA256 string
|
|
ReceiptID string
|
|
CreatedAt string
|
|
Merkle string
|
|
Scope string
|
|
VerifyURL string
|
|
Channel string
|
|
ByteLength int64
|
|
}
|
|
|
|
// LookupURL builds a public VeraeTime verify deep link (same as explorer
|
|
// scan-bar → /time, which auto-fills receipt/sha256 and runs Lookup).
|
|
// https://explorer.verae.ai/time?receipt=…&sha256=…
|
|
func LookupURL(sha256, receiptID string) string {
|
|
sha256 = strings.TrimSpace(sha256)
|
|
sha256 = strings.TrimPrefix(strings.ToLower(sha256), "0x")
|
|
receiptID = strings.TrimSpace(receiptID)
|
|
u := "https://explorer.verae.ai/time"
|
|
sep := "?"
|
|
if receiptID != "" {
|
|
u += sep + "receipt=" + receiptID
|
|
sep = "&"
|
|
}
|
|
if sha256 != "" {
|
|
u += sep + "sha256=" + sha256
|
|
}
|
|
return u
|
|
}
|
|
|
|
// Build returns the branded registration PDF using the same jsPDF layout as
|
|
// Verae Timestamping.app / register.html export when Node is available.
|
|
func Build(in Input) ([]byte, error) {
|
|
created := in.CreatedAt
|
|
if created == "" {
|
|
// Do not invent a ledger time — leave blank for the PDF field.
|
|
created = ""
|
|
}
|
|
scope := strings.ToUpper(strings.TrimSpace(in.Scope))
|
|
if scope == "" {
|
|
scope = "GLOBAL"
|
|
}
|
|
lookup := in.VerifyURL
|
|
if lookup == "" || strings.Contains(strings.ToLower(lookup), "mcp.verae.ai") {
|
|
lookup = LookupURL(in.SHA256, in.ReceiptID)
|
|
}
|
|
merkle := in.Merkle
|
|
if merkle == "" {
|
|
merkle = "See ledger lookup via receipt CID"
|
|
}
|
|
docName := in.DocumentName
|
|
if docName == "" {
|
|
docName = "(digest only)"
|
|
}
|
|
|
|
row := map[string]any{
|
|
"file": docName,
|
|
"sha256": in.SHA256,
|
|
"scope": scope,
|
|
"receipt_id": in.ReceiptID,
|
|
"created_at": created,
|
|
"merkle": merkle,
|
|
"lookup_url": lookup,
|
|
"size_bytes": in.ByteLength,
|
|
}
|
|
|
|
if b, err := buildWithNode(row); err == nil && len(b) > 1000 {
|
|
return b, nil
|
|
}
|
|
|
|
return buildFallbackGofpdf(in, docName, created, scope, merkle, lookup)
|
|
}
|
|
|
|
func buildWithNode(row map[string]any) ([]byte, error) {
|
|
script := findBuildScript()
|
|
if script == "" {
|
|
return nil, fmt.Errorf("registration-pdf build.mjs not found")
|
|
}
|
|
tmp, err := os.MkdirTemp("", "verae-regpdf-*")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer os.RemoveAll(tmp)
|
|
inPath := filepath.Join(tmp, "in.json")
|
|
outPath := filepath.Join(tmp, "out.pdf")
|
|
jb, err := json.Marshal(row)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := os.WriteFile(inPath, jb, 0o600); err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := exec.Command("node", script, inPath, outPath)
|
|
cmd.Dir = filepath.Dir(script)
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("node build.mjs: %w (%s)", err, stderr.String())
|
|
}
|
|
return os.ReadFile(outPath)
|
|
}
|
|
|
|
func findBuildScript() string {
|
|
candidates := []string{
|
|
"/Library/Application Support/Verae/PrintStamp/registration-pdf/build.mjs",
|
|
"/Users/marchon/research/PrintToVeraeTimeStamp/platform/registration-pdf/build.mjs",
|
|
filepath.Join("platform", "registration-pdf", "build.mjs"),
|
|
}
|
|
// Walk up from executable
|
|
if exe, err := os.Executable(); err == nil {
|
|
dir := filepath.Dir(exe)
|
|
candidates = append(candidates,
|
|
filepath.Join(dir, "registration-pdf", "build.mjs"),
|
|
filepath.Join(dir, "..", "registration-pdf", "build.mjs"),
|
|
filepath.Join(dir, "platform", "registration-pdf", "build.mjs"),
|
|
filepath.Join(dir, "..", "platform", "registration-pdf", "build.mjs"),
|
|
)
|
|
}
|
|
for _, c := range candidates {
|
|
if st, err := os.Stat(c); err == nil && !st.IsDir() {
|
|
abs, _ := filepath.Abs(c)
|
|
return abs
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Ensure created stays empty when unknown — used by tests.
|
|
var _ = time.Now
|