← back to Dw Photo Capture
sticker-watcher.js
65 lines
// Option B — Mac-side sticker watcher. Runs on the Mac attached to the Zebra printer (launchd).
// Every few seconds it asks FileMaker Cloud for records flagged `web_print_request`, triggers the
// EXISTING FileMaker Pro print script via AppleScript (`do script … with parameter <combo sku>`),
// then clears the flag so it prints once. Reuses this project's fm-client.js.
//
// Requires (env, set by the launchd plist):
// FM_CLOUD_HOST, FM_CLARIS_EMAIL, FM_CLARIS_PASSWORD, FM_COGNITO_USERPOOL, FM_COGNITO_CLIENTID
// FM_PRINT_FLAG_FIELD (default web_print_request)
// FM_PRINT_SCRIPT_SKU (your existing SKU-sticker FileMaker script name)
// FM_PRINT_SCRIPT_ADDR (your existing address-sticker FileMaker script name)
// STICKER_POLL_MS (default 4000)
// NOTE: your print script must accept the passed combo-sku as its script parameter and Go-To that
// record before printing (a 5-line wrapper is in FILEMAKER-STICKER-SETUP.md if it doesn't already).
// Do NOT run this AND the FileMaker OnTimer poller (Option A) at the same time — pick one; double-arm double-prints.
const fs = require('fs'), path = require('path');
// Load FM_* creds from the master filemaker-mcp .env if not already in the environment (so the
// launchd plist doesn't have to carry secrets — it only sets the print-script names).
try {
for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/filemaker-mcp/.env'), 'utf8').split('\n')) {
const m = /^(FM_[A-Z_]+)=(.*)$/.exec(l.trim());
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
}
} catch (e) {}
const { fmFind, fmSet } = require('./fm-client.js');
const { execFile } = require('child_process');
const DB = 'WALLPAPER', LAYOUT = 'Sample Requests via email';
const FLAG = process.env.FM_PRINT_FLAG_FIELD || 'web_print_request';
const SKU_SCRIPT = process.env.FM_PRINT_SCRIPT_SKU || 'PRINT SKU STICKER';
const ADDR_SCRIPT = process.env.FM_PRINT_SCRIPT_ADDR || 'PRINT ADDRESS STICKER';
const INTERVAL = Number(process.env.STICKER_POLL_MS) || 4000;
function log(...a) { console.log(new Date().toISOString(), ...a); }
// Trigger an existing FileMaker Pro script with the record's combo-sku as the parameter.
function doScript(name, param) {
return new Promise((resolve) => {
const esc = s => String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const osa = `tell application "FileMaker Pro" to do script "${esc(name)}" with parameter "${esc(param)}"`;
execFile('osascript', ['-e', osa], { timeout: 15000 }, (err, so, se) => resolve({ ok: !err, err: err && (se || err.message) }));
});
}
async function tick() {
let recs;
try { recs = (await fmFind(DB, LAYOUT, { [FLAG]: '*' }, { limit: 20 })).records; }
catch (e) { log('poll error:', e.message); return; }
for (const r of recs) {
const f = r.fieldData || {};
const flag = String(f[FLAG] || '').toLowerCase();
const key = f['combo sku'] || f['Mfr Pattern'] || r.recordId;
const script = flag === 'address' ? ADDR_SCRIPT : SKU_SCRIPT;
const out = await doScript(script, key);
if (out.ok) {
try { await fmSet(DB, LAYOUT, r.recordId, { [FLAG]: '' }); log('printed', flag, key, '(rec ' + r.recordId + ')'); }
catch (e) { log('printed but FLAG-clear failed for', r.recordId, e.message); }
} else {
log('PRINT FAILED for', key, '—', (out.err || '').toString().slice(0, 120));
}
}
}
log(`sticker-watcher up · flag=${FLAG} · sku="${SKU_SCRIPT}" addr="${ADDR_SCRIPT}" · every ${INTERVAL}ms`);
(async function loop() { for (;;) { await tick(); await new Promise(r => setTimeout(r, INTERVAL)); } })();