(function () { if (window.app) return; // desktop: preload bridge already present // Dev defaults; production injects window.BL_PL_BASE / window.BL_GW_BASE // (e.g. https://app.boxlogic.eu/trpc and https://files.boxlogic.eu). var PL_BASE = (window.BL_PL_BASE || 'http://localhost:3000/trpc').replace(/\/+$/, ''); var GW_BASE = (window.BL_GW_BASE || 'http://localhost:8091').replace(/\/+$/, ''); var TOKEN_KEY = 'bl_portal_token'; function getToken() { try { return sessionStorage.getItem(TOKEN_KEY) || null; } catch (e) { return null; } } function setToken(t) { try { t ? sessionStorage.setItem(TOKEN_KEY, t) : sessionStorage.removeItem(TOKEN_KEY); } catch (e) {} } // tRPC envelope (verified live against PL): query = GET /?input={json}, // mutation = POST / {json}; success → result.data.json; error → error.json. function unwrap(body) { if (body && body.error) { var e = body.error.json || body.error; var err = new Error((e && e.message) || 'PrintLogic error'); err.code = e && e.data && e.data.code; err.status = e && e.data && e.data.httpStatus; throw err; } return body && body.result && body.result.data ? body.result.data.json : undefined; } async function call(method, proc, input, auth) { var url = PL_BASE + '/' + proc; var opts = { method: method, headers: { 'content-type': 'application/json' } }; var tok = getToken(); if (auth && tok) opts.headers['authorization'] = 'Bearer ' + tok; if (method === 'GET') { if (input !== undefined) url += '?input=' + encodeURIComponent(JSON.stringify({ json: input })); } else { opts.body = JSON.stringify({ json: input == null ? null : input }); } var res = await fetch(url, opts); var body = null; try { body = await res.json(); } catch (e) {} if (body && (body.error || body.result)) return unwrap(body); var err = new Error('PrintLogic HTTP ' + res.status); err.status = res.status; throw err; } var q = function (p, i, a) { return call('GET', p, i, a); }; var m = function (p, i, a) { return call('POST', p, i, a); }; // Per-folder gateway token cache (PL mints 15-min tokens; reuse for ~13 min). var tokCache = {}; async function mintFileToken(id) { var c = tokCache[id]; if (c && (Date.now() - c.at) < 13 * 60 * 1000) return c.token; var r = await m('boxLogicFile.mintForCustomer', { folderId: id }, true); tokCache[id] = { token: r.token, at: Date.now() }; return r.token; } function mapProject(p) { var t = p.type || {}; return { id: p.folderId, status: p.status, title: p.title || null, has3D: !!t.has3D, customerEditable: !!t.customerEditable, uploadOnly: !!t.uploadOnly, cf2SelfServe: !!t.cf2SelfServe, typeCode: t.code || null, files: {}, // unknown at list time — the gateway is the source configured: true, // customers never enter configure mode isEmpty: false, }; } window.app = { __portal: true, login: async function (email, password) { try { var out = await m('customerAuth.login', { email: email, password: password }, false); setToken(out.token); return true; } catch (e) { setToken(null); if (e && (e.status === 401 || e.code === 'UNAUTHORIZED')) return false; throw e; } }, isAuthed: async function () { if (!getToken()) return false; try { await q('customerAuth.me', undefined, true); return true; } catch (e) { setToken(null); return false; } }, logout: async function () { try { await m('customerAuth.logout', null, true); } catch (e) {} setToken(null); }, listProjects: async function () { var rows = await q('boxLogicProject.listForCustomer', {}, true); return (rows || []).map(mapProject); }, // installAndSwitchTemplate uses this (portal only) → gateway URLs with a // freshly-minted, folder-scoped token. Desktop preload has no fileUrls, so // it keeps using the boxlogic:// cache protocol. fileUrls: async function (id) { var tok = await mintFileToken(id); var u = function (name) { return GW_BASE + '/gw/' + id + '/' + name + '?token=' + encodeURIComponent(tok); }; return { dieline: u(id + '_dieline.pdf'), glb: u(id + '.glb'), artwork: u(id + '_artwork.pdf'), template: u(id + '_template.json'), }; }, }; })();
PackagingProof
Awaiting PDF

Die-lines loaded

Click Edit to add artwork in Affinity
Layers
Artwork offset
x: 0   y: 0
3D model:
Drag to rotate  ·  Scroll to zoom
Rotate 90°
EngView render
Loading 3D model…
Applying artwork…
no file |
dieline.svg loaded — awaiting PDF upload