hh-shipments 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/public/app.js +113 -40
- package/public/extract.js +74 -9
- package/public/index.html +1 -1
- package/public/sw.js +1 -1
- package/public/vendor/zxing.min.js +1 -0
- package/src/extract.js +74 -8
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
+
"@zxing/library": "^0.23.0",
|
|
3
4
|
"bcryptjs": "^2.4.3",
|
|
4
5
|
"cookie-parser": "^1.4.6",
|
|
5
6
|
"express": "^4.21.2",
|
|
@@ -15,10 +16,10 @@
|
|
|
15
16
|
"name": "hh-shipments",
|
|
16
17
|
"scripts": {
|
|
17
18
|
"start": "node server.js",
|
|
18
|
-
"test": "node test/run-tests.js && node test/run-carriers-tests.js"
|
|
19
|
+
"test": "node test/run-extract-tests.js && node test/run-tests.js && node test/run-carriers-tests.js"
|
|
19
20
|
},
|
|
20
21
|
"type": "commonjs",
|
|
21
|
-
"version": "0.3.
|
|
22
|
+
"version": "0.3.26",
|
|
22
23
|
"bin": {
|
|
23
24
|
"hh-shipments": "server.js"
|
|
24
25
|
},
|
package/public/app.js
CHANGED
|
@@ -175,53 +175,120 @@ function loadTesseract() {
|
|
|
175
175
|
return tesseractLoading;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
//
|
|
179
|
-
// screenshots (dark
|
|
180
|
-
//
|
|
181
|
-
|
|
178
|
+
// "Better eyes": smart OCR pipeline that adapts to image type.
|
|
179
|
+
// - Chat screenshots (dark): invert, PSM 6 (uniform block)
|
|
180
|
+
// - Labels (light): crop to tracking zone, upscale, PSM 6
|
|
181
|
+
// - Falls back to full-image PSM 3 if targeted fails
|
|
182
|
+
async function ocrFile(file, statusEl) {
|
|
183
|
+
await loadTesseract();
|
|
184
|
+
statusEl.textContent = 'Reading screenshot…';
|
|
185
|
+
const isDark = await isDarkImage(file);
|
|
186
|
+
file = isDark ? await invertImage(file) : file;
|
|
187
|
+
|
|
188
|
+
const log = (m) => { if (m.status === 'recognizing text' && statusEl) statusEl.textContent = `Reading… ${Math.round(m.progress * 100)}%`; };
|
|
189
|
+
|
|
190
|
+
// Pass 1: targeted — for labels, crop to the tracking-number zone (bottom 45%)
|
|
191
|
+
// and upscale for Tesseract. For chats, use the full image.
|
|
192
|
+
let bestText = '';
|
|
193
|
+
if (!isDark) {
|
|
194
|
+
try {
|
|
195
|
+
const zoned = await cropAndUpscale(file, 0, 0.55, 1, 0.45, 2);
|
|
196
|
+
const r1 = await Tesseract.recognize(zoned, 'eng', {
|
|
197
|
+
logger: log,
|
|
198
|
+
tessedit_pageseg_mode: '6', // uniform block of text
|
|
199
|
+
});
|
|
200
|
+
bestText = r1.data.text || '';
|
|
201
|
+
// If the zone has a plausible tracking number, we're done
|
|
202
|
+
if (/(1Z|12|IZ|17)\s*[0-9A-Z)\]}(\[{|!]/i.test(bestText)) {
|
|
203
|
+
if (statusEl) statusEl.textContent = '';
|
|
204
|
+
return bestText;
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Pass 2: full image with PSM 6
|
|
210
|
+
try {
|
|
211
|
+
const r2 = await Tesseract.recognize(file, 'eng', {
|
|
212
|
+
logger: log,
|
|
213
|
+
tessedit_pageseg_mode: '6',
|
|
214
|
+
});
|
|
215
|
+
const t2 = r2.data.text || '';
|
|
216
|
+
if (t2.length > bestText.length) bestText = t2;
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
|
|
219
|
+
// Pass 3: fallback to default PSM 3 if we got nothing useful
|
|
220
|
+
if (!bestText || bestText.trim().length < 10) {
|
|
221
|
+
try {
|
|
222
|
+
const r3 = await Tesseract.recognize(file, 'eng', { logger: log });
|
|
223
|
+
const t3 = r3.data.text || '';
|
|
224
|
+
if (t3.length > bestText.length) bestText = t3;
|
|
225
|
+
} catch (e) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (statusEl) statusEl.textContent = '';
|
|
229
|
+
return bestText;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function isDarkImage(file) {
|
|
182
233
|
try {
|
|
183
234
|
const bmp = await createImageBitmap(file);
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
canvas.width = sw; canvas.height = sh;
|
|
189
|
-
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
235
|
+
const sw = Math.min(100, bmp.width), sh = Math.min(100, bmp.height);
|
|
236
|
+
const c = document.createElement('canvas');
|
|
237
|
+
c.width = sw; c.height = sh;
|
|
238
|
+
const ctx = c.getContext('2d', { willReadFrequently: true });
|
|
190
239
|
ctx.drawImage(bmp, 0, 0, sw, sh);
|
|
191
240
|
const px = ctx.getImageData(0, 0, sw, sh).data;
|
|
192
241
|
let sum = 0, n = 0;
|
|
193
242
|
for (let i = 0; i < px.length; i += 16) {
|
|
194
|
-
sum += 0.299 * px[i] + 0.587 * px[i
|
|
243
|
+
sum += 0.299 * px[i] + 0.587 * px[i+1] + 0.114 * px[i+2];
|
|
195
244
|
n++;
|
|
196
245
|
}
|
|
197
|
-
const avg = sum / n;
|
|
198
246
|
bmp.close();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
247
|
+
return (sum / n) < 110;
|
|
248
|
+
} catch (e) { return false; }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function invertImage(file) {
|
|
252
|
+
try {
|
|
253
|
+
const bmp = await createImageBitmap(file);
|
|
254
|
+
const c = document.createElement('canvas');
|
|
255
|
+
c.width = bmp.width; c.height = bmp.height;
|
|
256
|
+
const ctx = c.getContext('2d');
|
|
257
|
+
ctx.drawImage(bmp, 0, 0);
|
|
258
|
+
bmp.close();
|
|
259
|
+
const img = ctx.getImageData(0, 0, c.width, c.height);
|
|
208
260
|
const d = img.data;
|
|
209
261
|
for (let i = 0; i < d.length; i += 4) {
|
|
210
|
-
d[i] = 255 - d[i]; d[i
|
|
262
|
+
d[i] = 255 - d[i]; d[i+1] = 255 - d[i+1]; d[i+2] = 255 - d[i+2];
|
|
211
263
|
}
|
|
212
|
-
|
|
213
|
-
const blob = await new Promise((res) =>
|
|
264
|
+
ctx.putImageData(img, 0, 0);
|
|
265
|
+
const blob = await new Promise((res) => c.toBlob(res, 'image/jpeg', 0.92));
|
|
214
266
|
return blob ? new File([blob], 'inv.jpg', { type: 'image/jpeg' }) : file;
|
|
215
267
|
} catch (e) { return file; }
|
|
216
268
|
}
|
|
217
269
|
|
|
218
|
-
async function
|
|
219
|
-
await
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
270
|
+
async function cropAndUpscale(file, fx, fy, fw, fh, scale) {
|
|
271
|
+
const bmp = await createImageBitmap(file);
|
|
272
|
+
const sw = Math.round(bmp.width * fw), sh = Math.round(bmp.height * fh);
|
|
273
|
+
const c = document.createElement('canvas');
|
|
274
|
+
c.width = Math.round(sw * scale);
|
|
275
|
+
c.height = Math.round(sh * scale);
|
|
276
|
+
const ctx = c.getContext('2d');
|
|
277
|
+
ctx.drawImage(bmp, bmp.width * fx, bmp.height * fy, sw, sh, 0, 0, c.width, c.height);
|
|
278
|
+
bmp.close();
|
|
279
|
+
// Boost contrast for text
|
|
280
|
+
try {
|
|
281
|
+
const img = ctx.getImageData(0, 0, c.width, c.height);
|
|
282
|
+
const d = img.data;
|
|
283
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
284
|
+
const v = 0.299*d[i] + 0.587*d[i+1] + 0.114*d[i+2];
|
|
285
|
+
const nv = v < 140 ? Math.max(0, v - 40) : Math.min(255, v + 40);
|
|
286
|
+
d[i] = d[i+1] = d[i+2] = nv;
|
|
287
|
+
}
|
|
288
|
+
ctx.putImageData(img, 0, 0);
|
|
289
|
+
} catch (e) {}
|
|
290
|
+
const blob = await new Promise((res) => c.toBlob(res, 'image/jpeg', 0.95));
|
|
291
|
+
return new File([blob], 'crop.jpg', { type: 'image/jpeg' });
|
|
225
292
|
}
|
|
226
293
|
|
|
227
294
|
async function handlePhotoFile(f) {
|
|
@@ -314,15 +381,17 @@ async function fetchTimeout(url, opts = {}, ms = 60000) {
|
|
|
314
381
|
}
|
|
315
382
|
|
|
316
383
|
// ---- Barcode-first decoding (ZXing) ----
|
|
317
|
-
//
|
|
318
|
-
//
|
|
384
|
+
// Bundled locally at /vendor/zxing.min.js so a CDN outage can never
|
|
385
|
+
// silently kill it. A decoded barcode is checksum-verified (Code128
|
|
386
|
+
// mod-103), so it is essentially never wrong — far more reliable than
|
|
387
|
+
// OCR text for labels.
|
|
319
388
|
let zxingPromise = null;
|
|
320
389
|
function loadZxing() {
|
|
321
390
|
if (window.ZXing) return Promise.resolve();
|
|
322
391
|
if (!zxingPromise) {
|
|
323
392
|
zxingPromise = new Promise((res, rej) => {
|
|
324
393
|
const s = document.createElement('script');
|
|
325
|
-
s.src = '
|
|
394
|
+
s.src = '/vendor/zxing.min.js';
|
|
326
395
|
s.onload = res;
|
|
327
396
|
s.onerror = () => rej(new Error('barcode reader failed to load'));
|
|
328
397
|
document.head.appendChild(s);
|
|
@@ -357,8 +426,13 @@ async function ocrTopStrip(origFile, statusEl) {
|
|
|
357
426
|
function strictTracking(s) {
|
|
358
427
|
s = String(s || '').toUpperCase().replace(/[\s-]/g, '');
|
|
359
428
|
if (/^1Z[0-9A-Z]{16}$/.test(s)) return s; // UPS
|
|
360
|
-
|
|
361
|
-
|
|
429
|
+
// IMpb barcode: strip the routing prefix, return the tracking number.
|
|
430
|
+
// The routing portion alone is never a shipment.
|
|
431
|
+
var impb = (window.HHExtract && HHExtract.parseImpb) ? HHExtract.parseImpb(s) : null;
|
|
432
|
+
if (impb) return impb;
|
|
433
|
+
if (/^9[1-5]\d{18,20}$/.test(s)) return s; // USPS
|
|
434
|
+
if (/^420\d{9}$/.test(s)) return null; // IMpb routing — never a shipment
|
|
435
|
+
if (/^(\d{12}|\d{15}|\d{20})$/.test(s)) return s; // FedEx
|
|
362
436
|
if (/^\d{10,11}$/.test(s)) return s; // DHL
|
|
363
437
|
return null;
|
|
364
438
|
}
|
|
@@ -533,13 +607,12 @@ async function loadPending() {
|
|
|
533
607
|
async function reviewPendingImage(id) {
|
|
534
608
|
$('photo-result').innerHTML = `<img src="/api/intake/image/${id}" style="max-width:100%;border-radius:10px"><p class="muted">Extracting…</p>`;
|
|
535
609
|
try {
|
|
536
|
-
await loadTesseract();
|
|
537
610
|
const resp = await fetch(`/api/intake/image/${id}`);
|
|
538
611
|
const blob = await resp.blob();
|
|
539
|
-
const file =
|
|
540
|
-
const
|
|
612
|
+
const file = new File([blob], 'pending.jpg', { type: blob.type || 'image/jpeg' });
|
|
613
|
+
const text = await ocrFile(file, null);
|
|
541
614
|
await api.patch('/api/intake/' + id, { state: 'reviewed' });
|
|
542
|
-
await runQuick(
|
|
615
|
+
await runQuick(text, $('photo-result'));
|
|
543
616
|
loadPending();
|
|
544
617
|
} catch (e) { $('photo-result').innerHTML = `<p class="error">${esc(e.message)}</p>`; }
|
|
545
618
|
}
|
package/public/extract.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
var PATTERNS = [
|
|
10
10
|
// UPS: 1Z + 16 alphanumerics
|
|
11
11
|
{ carrier: 'ups', confidence: 'high', re: /\b(1Z[0-9A-Z]{16})\b/gi },
|
|
12
|
-
// USPS: 20-22 digits starting
|
|
13
|
-
{ carrier: 'usps', confidence: 'high', re: /\b(9[
|
|
12
|
+
// USPS: 20-22 digits starting 91-95 (9400..., 9305..., 9205..., 9101..., etc.)
|
|
13
|
+
{ carrier: 'usps', confidence: 'high', re: /\b(9[1-5]\d{18,20})\b/g },
|
|
14
14
|
// USPS S10 international: 2 letters + 9 digits + 2 letters (ends US = USPS)
|
|
15
15
|
{ carrier: 'usps', confidence: 'medium', re: /\b([A-Z]{2}\d{9}US)\b/g },
|
|
16
16
|
// FedEx SmartPost / Ground 22-digit starting 96
|
|
@@ -95,9 +95,14 @@
|
|
|
95
95
|
anchorZones(text).forEach(function (z) {
|
|
96
96
|
runPatterns(z.text, found, seen, z.carrier);
|
|
97
97
|
spacedUpsIn(z.text, found, seen, 'high');
|
|
98
|
+
spacedDigitGroupsIn(z.text, found, seen, 'high');
|
|
98
99
|
});
|
|
99
100
|
// 1) Whole-text strict patterns
|
|
100
101
|
runPatterns(text, found, seen, false);
|
|
102
|
+
// 1b) Spaced digit groups: labels print "9305 5110 4390 0011 2062 42"
|
|
103
|
+
// with spaces, which strict PATTERNS cannot match. Without this pass a
|
|
104
|
+
// perfectly-read number is silently dropped.
|
|
105
|
+
spacedDigitGroupsIn(text, found, seen, 'medium');
|
|
101
106
|
// 2) OCR-tolerant recovery pass
|
|
102
107
|
extractOcrTolerant(text, found, seen);
|
|
103
108
|
var rank = { high: 0, medium: 1, low: 2 };
|
|
@@ -142,6 +147,9 @@
|
|
|
142
147
|
var num = normalize(m[1]);
|
|
143
148
|
if (seen[num]) continue;
|
|
144
149
|
seen[num] = true;
|
|
150
|
+
// Never accept an IMpb routing prefix (420 + 9 digits) as a FedEx
|
|
151
|
+
// number — it is postal routing, not a shipment.
|
|
152
|
+
if (p.carrier === 'fedex' && /^420\d{9}$/.test(num)) continue;
|
|
145
153
|
// Context boost: carrier word near the number, or a hot-zone anchor
|
|
146
154
|
// naming this carrier (a label's "UPS GROUND" line means the number
|
|
147
155
|
// under it really is UPS).
|
|
@@ -162,12 +170,61 @@
|
|
|
162
170
|
function isValidFormat(number, carrier) {
|
|
163
171
|
var n = normalize(number);
|
|
164
172
|
if (carrier === 'ups') return /^1Z[0-9A-Z]{16}$/.test(n);
|
|
165
|
-
if (carrier === 'usps') return /^9[
|
|
166
|
-
|
|
173
|
+
if (carrier === 'usps') return /^9[1-5]\d{18,20}$/.test(n) || /^[A-Z]{2}\d{9}US$/.test(n);
|
|
174
|
+
// FedEx: never accept an IMpb routing prefix (420 + 9 digits) — it is
|
|
175
|
+
// postal routing, not a shipment number.
|
|
176
|
+
if (carrier === 'fedex') return /^(96\d{20}|\d{12}|\d{15}|\d{20})$/.test(n) && !/^420\d{9}$/.test(n);
|
|
167
177
|
if (carrier === 'dhl') return /^\d{10,11}$/.test(n) || /^[A-Z]{2,4}\d{10,20}$/.test(n);
|
|
168
178
|
return false;
|
|
169
179
|
}
|
|
170
180
|
|
|
181
|
+
// Intelligent Mail package barcode: 420 + routing digits + USPS tracking,
|
|
182
|
+
// e.g. 420078742956 + 9305511043900011206242 (34 digits). The full barcode
|
|
183
|
+
// is NOT the tracking number — return the trailing tracking number. The
|
|
184
|
+
// routing portion must never pass as a shipment number.
|
|
185
|
+
function parseImpb(s) {
|
|
186
|
+
var num = normalize(s);
|
|
187
|
+
if (!/^420\d{23,31}$/.test(num)) return null; // 26–34 digits total
|
|
188
|
+
var m = /(9[1-5]\d{18,20})$/.exec(num);
|
|
189
|
+
return m ? m[1] : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// A spaced/dashed digit run is only plausible as a tracking number when
|
|
193
|
+
// every group has 2+ digits — this rejects ZIP+4 glued to a nearby lone
|
|
194
|
+
// digit ("07874-2956 1 lb"), phone fragments ("1-800-555-1234"), etc.
|
|
195
|
+
function validSpacedGroups(raw) {
|
|
196
|
+
var groups = String(raw).split(/[\s-]+/);
|
|
197
|
+
if (groups.length < 2) return true; // contiguous — decided elsewhere
|
|
198
|
+
for (var i = 0; i < groups.length; i++) if (groups[i].length < 2) return false;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Digit groups split by spaces/dashes: "9305 5110 4390 0011 2062 42".
|
|
203
|
+
// Strict PATTERNS only match contiguous runs, so this pass exists for the
|
|
204
|
+
// standard label printing format. Only fires when the raw match actually
|
|
205
|
+
// contains a separator (contiguous runs are already covered by PATTERNS).
|
|
206
|
+
// Joined candidates must validate against a carrier format; IMpb barcodes
|
|
207
|
+
// yield their tracking number, never the routing.
|
|
208
|
+
function spacedDigitGroupsIn(text, found, seen, conf) {
|
|
209
|
+
var re = /\b\d(?:[\s-]?\d){9,33}\b/g, m;
|
|
210
|
+
while ((m = re.exec(text)) !== null) {
|
|
211
|
+
if (!/[\s-]/.test(m[0])) continue;
|
|
212
|
+
if (!validSpacedGroups(m[0])) continue;
|
|
213
|
+
var num = normalize(m[0]);
|
|
214
|
+
if (!num || seen[num]) continue;
|
|
215
|
+
var impb = parseImpb(num);
|
|
216
|
+
if (impb) {
|
|
217
|
+
seen[num] = true;
|
|
218
|
+
if (!seen[impb]) { seen[impb] = true; found.push({ number: impb, carrier: 'usps', confidence: conf }); }
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
var carrier = guessCarrier(num);
|
|
222
|
+
if (carrier === 'unknown') { seen[num] = true; continue; }
|
|
223
|
+
seen[num] = true;
|
|
224
|
+
found.push({ number: num, carrier: carrier, confidence: conf });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
171
228
|
function guessCarrier(num) {
|
|
172
229
|
var carriers = ['ups', 'usps', 'fedex', 'dhl'];
|
|
173
230
|
for (var i = 0; i < carriers.length; i++) if (isValidFormat(num, carriers[i])) return carriers[i];
|
|
@@ -194,13 +251,20 @@
|
|
|
194
251
|
// a) UPS number split by spaces/dashes (whole-text fallback; the anchor
|
|
195
252
|
// pass already tried the hot zones at high confidence)
|
|
196
253
|
spacedUpsIn(text, found, seen, 'medium');
|
|
197
|
-
// b) long digit-like runs with OCR confusions -> fix -> re-match digit formats
|
|
198
|
-
|
|
254
|
+
// b) long digit-like runs with OCR confusions -> fix -> re-match digit formats.
|
|
255
|
+
// Space-aware: OCR often reads "9305 51O0 ..." with both mangled chars
|
|
256
|
+
// AND spaces, so runs may contain separators. Runs up to 34 chars so a
|
|
257
|
+
// full 34-digit IMpb barcode is tested whole (parseImpb) instead of
|
|
258
|
+
// being split into chunks that false-positive as DHL/FedEx.
|
|
259
|
+
var runRe = /[0-9OILSBoilszb](?:[\s-]?[0-9OILSBoilszb]){9,33}/g, run;
|
|
199
260
|
while ((run = runRe.exec(text)) !== null) {
|
|
261
|
+
if (/[\s-]/.test(run[0]) && !validSpacedGroups(run[0])) continue;
|
|
200
262
|
var fixed = normalize(ocrDigitFix(run[0]));
|
|
201
|
-
|
|
263
|
+
var impbFixed = parseImpb(fixed);
|
|
264
|
+
if (impbFixed) { push(impbFixed, 'usps', 'medium'); continue; }
|
|
265
|
+
if (/^9[1-5]\d{18,20}$/.test(fixed)) push(fixed, 'usps', 'medium');
|
|
202
266
|
else if (/^96\d{20}$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
203
|
-
else if (/^(\d{12}|\d{15}|\d{20})$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
267
|
+
else if (/^(\d{12}|\d{15}|\d{20})$/.test(fixed) && !/^420\d{9}$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
204
268
|
else if (/^\d{11}$/.test(fixed)) push(fixed, 'dhl', 'medium');
|
|
205
269
|
else if (/^\d{10}$/.test(fixed)) push(fixed, 'dhl', 'low');
|
|
206
270
|
}
|
|
@@ -214,6 +278,7 @@
|
|
|
214
278
|
});
|
|
215
279
|
tokens.sort(function (a, b) { return b.length - a.length; });
|
|
216
280
|
tokens.slice(0, 3).forEach(function (t) {
|
|
281
|
+
if (/^420\d{9}$/.test(t)) return; // IMpb routing — never a shipment
|
|
217
282
|
// UPS "1Z" misread as "12"/"IZ" in a joined (space-free) token
|
|
218
283
|
var fixed = repairUpsZ(t);
|
|
219
284
|
if (fixed) { push(fixed, 'ups', 'medium'); return; }
|
|
@@ -225,7 +290,7 @@
|
|
|
225
290
|
});
|
|
226
291
|
}
|
|
227
292
|
|
|
228
|
-
var api = { extractTrackingNumbers: extractTrackingNumbers, extractContext: extractContext, normalize: normalize, isValidFormat: isValidFormat };
|
|
293
|
+
var api = { extractTrackingNumbers: extractTrackingNumbers, extractContext: extractContext, normalize: normalize, isValidFormat: isValidFormat, parseImpb: parseImpb };
|
|
229
294
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
230
295
|
globalThis.HHExtract = api;
|
|
231
296
|
})();
|
package/public/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
6
|
<meta name="theme-color" content="#0a84ff">
|
|
7
|
-
<meta name="app-version" content="0.3.
|
|
7
|
+
<meta name="app-version" content="0.3.25">
|
|
8
8
|
<title>HH Shipments</title>
|
|
9
9
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
10
10
|
<link rel="apple-touch-icon" href="/icon-192.png">
|
package/public/sw.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
CACHE name includes the app version (injected by the server), so every
|
|
3
3
|
deploy installs a fresh shell and deletes the old one. */
|
|
4
4
|
const CACHE = 'hh-ship-__APP_VERSION__';
|
|
5
|
-
const SHELL = ['/', '/index.html', '/styles.css', '/app.js', '/extract.js', '/manifest.webmanifest'];
|
|
5
|
+
const SHELL = ['/', '/index.html', '/styles.css', '/app.js', '/extract.js', '/vendor/zxing.min.js', '/manifest.webmanifest'];
|
|
6
6
|
|
|
7
7
|
self.addEventListener('install', (e) => {
|
|
8
8
|
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting()));
|