@ham2k/extension-sdk 0.10.1 → 0.11.0
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/dist/activityExports.js +8 -4
- package/dist/index.d.ts +25 -2
- package/docs/distribution.md +152 -103
- package/docs/hooks.md +29 -2
- package/docs/settings.md +1 -1
- package/package.json +1 -1
- package/samples/k2hrc-cqww/src/index.ts +2 -2
package/dist/activityExports.js
CHANGED
|
@@ -83,7 +83,8 @@ function activityExportHook(rules) {
|
|
|
83
83
|
exportData: args.exportData,
|
|
84
84
|
exportTitle: args.exportTitle,
|
|
85
85
|
mainHandler: rules.key,
|
|
86
|
-
includeFieldsFrom: rules.includeFieldsFrom
|
|
86
|
+
includeFieldsFrom: rules.includeFieldsFrom,
|
|
87
|
+
operatorFallback: rules.operatorFallback
|
|
87
88
|
});
|
|
88
89
|
return {
|
|
89
90
|
filename: filenameFor(args.operation, args.qsos, wanted, args.compactFilenames),
|
|
@@ -155,7 +156,8 @@ function huntingExportHook(rules) {
|
|
|
155
156
|
// key ('pota'), which is what its `adifFields` hook registers under;
|
|
156
157
|
// the '-hunter' suffix belongs to the export registration alone.
|
|
157
158
|
mainHandler: rules.key,
|
|
158
|
-
includeFieldsFrom: rules.includeFieldsFrom
|
|
159
|
+
includeFieldsFrom: rules.includeFieldsFrom,
|
|
160
|
+
operatorFallback: rules.operatorFallback
|
|
159
161
|
});
|
|
160
162
|
return {
|
|
161
163
|
// The FULL qsos list, matching the option's filename — see
|
|
@@ -177,7 +179,8 @@ async function adifForExport({
|
|
|
177
179
|
exportData,
|
|
178
180
|
exportTitle,
|
|
179
181
|
mainHandler,
|
|
180
|
-
includeFieldsFrom
|
|
182
|
+
includeFieldsFrom,
|
|
183
|
+
operatorFallback
|
|
181
184
|
}) {
|
|
182
185
|
const { hooks } = await import("@ham2k/extension-sdk");
|
|
183
186
|
const entries = await hooks.invokeOne("export", "adif", "generateExport", {
|
|
@@ -193,7 +196,8 @@ async function adifForExport({
|
|
|
193
196
|
exportData,
|
|
194
197
|
exportTitle,
|
|
195
198
|
mainHandler,
|
|
196
|
-
includeFieldsFrom
|
|
199
|
+
includeFieldsFrom,
|
|
200
|
+
operatorFallback
|
|
197
201
|
});
|
|
198
202
|
const content = entries.find((e) => e.ok)?.value?.content;
|
|
199
203
|
if (!content) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ham2k/extension-sdk 0.
|
|
1
|
+
// @ham2k/extension-sdk 0.11.0
|
|
2
2
|
/** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
|
|
3
3
|
export interface PanelScene {
|
|
4
4
|
version: 1;
|
|
@@ -719,7 +719,9 @@ export interface ExportRequest {
|
|
|
719
719
|
includePrivateData?: boolean;
|
|
720
720
|
mainHandler?: string;
|
|
721
721
|
includeFieldsFrom?: string[];
|
|
722
|
+
operatorFallback?: OperatorFallback;
|
|
722
723
|
}
|
|
724
|
+
export type OperatorFallback = "base" | "station";
|
|
723
725
|
export interface ExportResult {
|
|
724
726
|
filename: string;
|
|
725
727
|
mimeType: string;
|
|
@@ -1058,6 +1060,24 @@ export interface SyncTransportHook {
|
|
|
1058
1060
|
ref?: string | null;
|
|
1059
1061
|
error?: string;
|
|
1060
1062
|
}>;
|
|
1063
|
+
uploadStashFile?(args: {
|
|
1064
|
+
file: {
|
|
1065
|
+
filename: string;
|
|
1066
|
+
content: string;
|
|
1067
|
+
mimeType?: string;
|
|
1068
|
+
format?: string;
|
|
1069
|
+
};
|
|
1070
|
+
groupUuid: string;
|
|
1071
|
+
groupTitle?: string;
|
|
1072
|
+
}, ctx: HookContext): Promise<{
|
|
1073
|
+
uploaded?: boolean;
|
|
1074
|
+
error?: string;
|
|
1075
|
+
fatal?: boolean;
|
|
1076
|
+
}>;
|
|
1077
|
+
listStashFiles?(args: {
|
|
1078
|
+
limit?: number;
|
|
1079
|
+
}, ctx: HookContext): Promise<Record<string, JSONValue>>;
|
|
1080
|
+
emailStashLink?(args: Record<string, never>, ctx: HookContext): Promise<Record<string, JSONValue>>;
|
|
1061
1081
|
}
|
|
1062
1082
|
export interface AccountField {
|
|
1063
1083
|
key: string;
|
|
@@ -1761,6 +1781,7 @@ export interface ActivityExportRules {
|
|
|
1761
1781
|
activationType: string;
|
|
1762
1782
|
icon?: string;
|
|
1763
1783
|
includeFieldsFrom?: string[];
|
|
1784
|
+
operatorFallback?: OperatorFallback;
|
|
1764
1785
|
}
|
|
1765
1786
|
export declare function activityExportHook(rules: ActivityExportRules): {
|
|
1766
1787
|
getExportTypes(): Promise<ExportTypeDefinition[]>;
|
|
@@ -1775,13 +1796,14 @@ export interface HuntingExportRules {
|
|
|
1775
1796
|
icon?: string;
|
|
1776
1797
|
color?: string;
|
|
1777
1798
|
includeFieldsFrom?: string[];
|
|
1799
|
+
operatorFallback?: OperatorFallback;
|
|
1778
1800
|
}
|
|
1779
1801
|
export declare function huntingExportHook(rules: HuntingExportRules): {
|
|
1780
1802
|
getExportTypes(): Promise<ExportTypeDefinition[]>;
|
|
1781
1803
|
suggestExportOptions({ operation, qsos, compactFilenames }: ExportOptionsRequest, ctx: HookContext): Promise<ExportOption[]>;
|
|
1782
1804
|
generateExport(args: ExportRequest, ctx: HookContext): Promise<ExportResult>;
|
|
1783
1805
|
};
|
|
1784
|
-
export declare function adifForExport({ operation, qsos, segments, includePrivateData, includeLookupData, exportSettings, exportData, exportTitle, mainHandler, includeFieldsFrom, }: {
|
|
1806
|
+
export declare function adifForExport({ operation, qsos, segments, includePrivateData, includeLookupData, exportSettings, exportData, exportTitle, mainHandler, includeFieldsFrom, operatorFallback, }: {
|
|
1785
1807
|
operation: Record<string, JSONValue>;
|
|
1786
1808
|
qsos: Record<string, JSONValue>[];
|
|
1787
1809
|
segments?: OperationSegmentPayload[];
|
|
@@ -1792,6 +1814,7 @@ export declare function adifForExport({ operation, qsos, segments, includePrivat
|
|
|
1792
1814
|
exportTitle?: string;
|
|
1793
1815
|
mainHandler: string;
|
|
1794
1816
|
includeFieldsFrom?: string[];
|
|
1817
|
+
operatorFallback?: OperatorFallback;
|
|
1795
1818
|
}): Promise<string>;
|
|
1796
1819
|
export interface ActivityAdifImportRules {
|
|
1797
1820
|
sig: string;
|
package/docs/distribution.md
CHANGED
|
@@ -310,12 +310,10 @@ packer refuses anything else.
|
|
|
310
310
|
There is no signing yet, so anyone could claim it, and the key is what the app
|
|
311
311
|
shows when it says where a hook came from.
|
|
312
312
|
|
|
313
|
-
The extensions built into the app use bare keys (`
|
|
314
|
-
reserved too: hook identity has to stay unambiguous
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
the packer cannot, because a standalone tool has no way to know what the app it
|
|
318
|
-
will be installed into contains.
|
|
313
|
+
The core extensions built into the app use bare keys (`adif`, `scoring`, …).
|
|
314
|
+
Those are reserved too: hook identity has to stay unambiguous. The app refuses
|
|
315
|
+
any collision at install — the packer cannot, because a standalone tool has no
|
|
316
|
+
way to know what the app it will be installed into contains.
|
|
319
317
|
|
|
320
318
|
## What an installed extension does not get
|
|
321
319
|
|
|
@@ -325,14 +323,14 @@ follows where the code came from, not what it calls itself. A feature that
|
|
|
325
323
|
needs one degrades the same way it does in a build without that value
|
|
326
324
|
configured.
|
|
327
325
|
|
|
328
|
-
The sandbox is otherwise the same one the
|
|
326
|
+
The sandbox is otherwise the same one the core extensions run in — no
|
|
329
327
|
filesystem, no network beyond the `domains` and `webSockets` the manifest
|
|
330
328
|
declares and the user approved, and timers only within their budget
|
|
331
329
|
(README's "Timers").
|
|
332
330
|
|
|
333
331
|
## Installing one
|
|
334
332
|
|
|
335
|
-
**Settings → Extensions → Install from file…**, pick the `.h2kext`, and agree
|
|
333
|
+
**Settings → Extensions & Features → Install from file…**, pick the `.h2kext`, and agree
|
|
336
334
|
to what it asks for. The runtime restarts with it loaded, and the extension
|
|
337
335
|
appears in its own category alongside the app's own, marked *Installed* and
|
|
338
336
|
with a way to remove it again.
|
|
@@ -381,108 +379,126 @@ came from. Nothing is written until the operator agrees.
|
|
|
381
379
|
|
|
382
380
|
### Pre-loaded extensions
|
|
383
381
|
|
|
384
|
-
The app
|
|
385
|
-
`
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
The app builds in only its **core** extensions — the ones that are the app
|
|
383
|
+
itself: `adif`, `scoring`, `lookups`, `templates`, the command sets and
|
|
384
|
+
`ham2k-lofi` sync. Every other extension is a catalog bundle, and the app ships
|
|
385
|
+
some of them already packaged under `app/assets/preloaded-extensions/`,
|
|
386
|
+
installing them into the ordinary store on first run. Afterwards they are
|
|
387
|
+
ordinary installed extensions — the same rows, the same update check, the same
|
|
388
|
+
Update and uninstall — so an operator starts with a working app and the catalog
|
|
389
|
+
can move it forward from there.
|
|
390
|
+
|
|
391
|
+
Which ones ship is staged. Today it is the catalog twin of every extension the
|
|
392
|
+
app used to build in (see [Retired built-ins](#retired-built-ins)), so that an
|
|
393
|
+
install moving off them has every one to hand with no network. Once installs
|
|
394
|
+
still on a build with the built-ins are few enough, it shrinks to the eight a
|
|
395
|
+
new install needs — `ham2k-pota`, `ham2k-sota`, `ham2k-wwff` and the five
|
|
396
|
+
lookups — and the rest are fetched from the catalog when switched on
|
|
397
|
+
([HALO-743](https://cabo.ham2k.com/halo/c/743)). The app's own tests rely on
|
|
398
|
+
only those eight, the core, and the never-shipped samples, so the shrink
|
|
399
|
+
touches none of them.
|
|
400
|
+
|
|
401
|
+
**They have to be kept current.** A pre-load is frozen into the binary, so
|
|
402
|
+
every release must carry the catalog's current stable release of each one: an
|
|
403
|
+
older one starts every new install behind the catalog until its first update
|
|
404
|
+
check. `node scripts/preloaded-extensions.mjs check` compares them with what
|
|
405
|
+
the catalog serves, byte for byte and at the app's extension API level;
|
|
406
|
+
`node scripts/preloaded-extensions.mjs update` replaces any that are behind
|
|
407
|
+
(`update <key>` adds one). Publishing a new release of a pre-loaded extension
|
|
408
|
+
is therefore also a change to this repository, and the release cut refuses to
|
|
409
|
+
ship pre-loads that fail the check ([releases.md §6.4](https://github.com/ham2k/halo/blob/main/docs/releases.md)).
|
|
390
410
|
|
|
391
411
|
They are installed once, not every launch. A record of the highest version
|
|
392
412
|
ever pre-loaded per key is what makes that true: an app update carrying a
|
|
393
413
|
newer bundle installs it, a catalog release already ahead of it is left
|
|
394
414
|
alone, and a key the operator uninstalled stays gone.
|
|
395
415
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
(`
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
the
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
from the listing fails the upgrade and is asked for again next launch: a
|
|
479
|
-
listing lacks a key the same way whether the catalog will never serve it or has
|
|
480
|
-
not published it yet, and only the app knows which. A built-in retired from the
|
|
481
|
-
catalog on purpose joins that list in the same change.
|
|
416
|
+
An installed bundle may not claim a key the app builds in, and a pre-load is
|
|
417
|
+
no exception: it goes through the same door as any other install.
|
|
418
|
+
|
|
419
|
+
**Build secrets are not among what they get.** Every value a twin once needed —
|
|
420
|
+
SOTA's OAuth client id, WWFF's API key — ships as a constant inside the
|
|
421
|
+
extension that uses it, because these bundles are served to anyone who asks and
|
|
422
|
+
a `.h2kext` is a zip. An installed extension sees no build secret, whatever key
|
|
423
|
+
it claims and wherever it came from.
|
|
424
|
+
|
|
425
|
+
### Retired built-ins
|
|
426
|
+
|
|
427
|
+
The app used to build in forty-one more extensions — the activities, contests,
|
|
428
|
+
lookups, a spot source and a dashboard panel. Their catalog copies are the same
|
|
429
|
+
extensions under the reserved prefix (`pota` became `ham2k-pota`; `ham2k-lookup`
|
|
430
|
+
kept its name), because the catalog cannot serve a bare key. Everything the app
|
|
431
|
+
stores per extension hangs off the name — its settings group, its panel values,
|
|
432
|
+
its account credentials, the operator's on/off choice — so each twin inherits
|
|
433
|
+
from its built-in. The app keeps a table of the retired keys and whether each
|
|
434
|
+
was on by default (`kRetiredBuiltIns`), since an install may skip any number of
|
|
435
|
+
releases between the last one with its built-ins and this one; it never shrinks.
|
|
436
|
+
|
|
437
|
+
**The move.** An install that ran the built-ins — one whose default profile had
|
|
438
|
+
a callsign before its extension state was first recorded
|
|
439
|
+
(`seedExtensionsMigratedDefault`) — moves onto their twins at its first boot on
|
|
440
|
+
a build without them, unasked: there is nothing left to run instead. The boot's
|
|
441
|
+
catch-up (`catchUpOnCatalogExtensions`) carries each built-in's on/off choice
|
|
442
|
+
onto its twin, over whatever an earlier attempt left there; installs the
|
|
443
|
+
pre-loads, which carry the built-in's settings, panel values and credentials on
|
|
444
|
+
a twin's first install, and its default where the operator never chose; then
|
|
445
|
+
fetches the twin of any other built-in the operator had switched on. It records
|
|
446
|
+
the move done once the pre-load has run, so a boot killed half way carries the
|
|
447
|
+
choices again next time. It needs no network while every twin is pre-loaded.
|
|
448
|
+
The boot starts the extension runtime before settings are wired in, so a
|
|
449
|
+
catch-up that finds none does not count, and wiring them runs it.
|
|
450
|
+
Choices keep being carried after the move, too: onboarding and the
|
|
451
|
+
`ONBOARD_AS` define name activities by their built-in keys, and
|
|
452
|
+
`offeredKeyFor` writes each choice under both names, so a twin that arrives
|
|
453
|
+
later still inherits it.
|
|
454
|
+
|
|
455
|
+
Once moved, the operator sees one notice — not a dialog, since the move was not
|
|
456
|
+
theirs to decide — saying their extensions now come from the catalog, naming
|
|
457
|
+
anything that did not come along, and counting the twins still to arrive from a
|
|
458
|
+
catalog that could not be reached. Any catch-up, the move or not, raises the
|
|
459
|
+
same notice when an extension was put aside or the runtime would not start at
|
|
460
|
+
all, and says so in place of "ready".
|
|
461
|
+
|
|
462
|
+
A pre-load is recorded as offered only once it installed, or when the store
|
|
463
|
+
refuses the bundle itself: a keychain that would not open or a full disk is
|
|
464
|
+
tried again next launch, and meanwhile the adoption fetches the catalog's copy
|
|
465
|
+
if the operator had it on. A twin the pre-load did install is never fetched
|
|
466
|
+
back after the operator uninstalls it. A choice stored under the pre-load's own
|
|
467
|
+
name — `ham2k-lookup`'s, which kept its name — is kept rather than dropped as
|
|
468
|
+
stale.
|
|
469
|
+
|
|
470
|
+
**What does not come along.** A few built-ins have no twin on purpose — `qp`,
|
|
471
|
+
which the catalog carries as one extension per party instead, and `stateparks`,
|
|
472
|
+
one per event — and the app names them itself (`kBuiltInsWithoutTwin`). The
|
|
473
|
+
notice names each one the operator had switched on, since it is where they learn
|
|
474
|
+
it is gone. Any other twin missing from the listing is asked for again next
|
|
475
|
+
launch: a listing lacks a key the same way whether the catalog will never serve
|
|
476
|
+
it or has not published it yet, and only the app knows which. A built-in
|
|
477
|
+
retired from the catalog on purpose joins that list in the same change.
|
|
478
|
+
|
|
479
|
+
**A twin that will not run.** A bundle the engine will not evaluate takes the
|
|
480
|
+
whole runtime with it, so the catch-up puts that one aside — the boot names the
|
|
481
|
+
bundle it died on — and restarts once more, the notice naming it in a sentence
|
|
482
|
+
of its own. Refused rather than withdrawn: the panel still lists it, badged,
|
|
483
|
+
because the row is where an operator sees that something they installed is
|
|
484
|
+
doing nothing, and updating it there is what lifts the refusal. Keyed by the
|
|
485
|
+
VERSION that failed, so a fixed release arrives as an ordinary update and
|
|
486
|
+
clears it. Only an INSTALLED extension is ever put aside; a core one that will
|
|
487
|
+
not evaluate is a defect in the app, and skipping it would ship an install
|
|
488
|
+
quietly missing part of itself. A refused extension is treated like a revoked
|
|
489
|
+
one in everything else: it keeps its switch, and its downloaded data expires on
|
|
490
|
+
the same clock as any extension the app is not loading. A failure that names no
|
|
491
|
+
bundle — a kernel that will not load, an extension that throws while
|
|
492
|
+
ACTIVATING — puts nothing aside, and is reported.
|
|
493
|
+
|
|
494
|
+
A twin fills the same lookup categories its built-in did, under data-file keys
|
|
495
|
+
of its own. Each retired built-in is stamped disabled, so a week on the
|
|
496
|
+
stale-data purge clears the data files it left — leaving alone the rows a live
|
|
497
|
+
extension's data file still fills.
|
|
482
498
|
|
|
483
499
|
### Installing from the catalog
|
|
484
500
|
|
|
485
|
-
**Settings → Extensions
|
|
501
|
+
**Settings → Extensions & Features** lists what [catalog.ham2k.net](https://catalog.ham2k.net)
|
|
486
502
|
publishes on its `stable` channel, in the catalog's own order for the
|
|
487
503
|
operator's callsign (`relevance`, above). Install and Update go through the same consent screen as a
|
|
488
504
|
file, over bytes that must hash to the sha256 the listing gives — a mismatch
|
|
@@ -496,6 +512,39 @@ catalog's own bundle links (`…/api/v1/extensions/<key>/versions/<version>/bund
|
|
|
496
512
|
take this verified path when they arrive as a deep link; every other host
|
|
497
513
|
takes the plain one above.
|
|
498
514
|
|
|
515
|
+
### Release tracks
|
|
516
|
+
|
|
517
|
+
A catalog channel — `stable`, `unstable` or `bleeding` — says how finished a
|
|
518
|
+
release is, and the catalog cascades: asking for `unstable` sees unstable and
|
|
519
|
+
stable releases. The panel's listing asks for `stable`. Under developer mode,
|
|
520
|
+
**Developer → Extension Catalog → Catalog Search Tracks** (Only Stable,
|
|
521
|
+
Unstable or better, Bleeding or better) widens it; turning developer mode off
|
|
522
|
+
puts the listing back on stable without touching anything installed. Only
|
|
523
|
+
the panel's listing reads it: fetching the catalog twins of retired built-ins
|
|
524
|
+
reads stable, since those install with no consent screen, and restoring a
|
|
525
|
+
missing extension reads the track it followed.
|
|
526
|
+
|
|
527
|
+
The other way to an unfinished release is a link: the catalog's install
|
|
528
|
+
button, or an address pasted into the panel's search field — a catalog
|
|
529
|
+
bundle URL, an extension's page (`/extensions/<key>?channel=unstable`, which
|
|
530
|
+
installs that channel's current release), the `install_extension` deep link
|
|
531
|
+
itself, or an https URL ending in `.h2kext`; anything else is a search. A
|
|
532
|
+
bundle URL is resolved against the listing on `bleeding`, which sees every
|
|
533
|
+
release, and the version's channel is the steadiest one it is approved on. A
|
|
534
|
+
page link records the channel it asked for as the track even when the release
|
|
535
|
+
there turns out to be steadier — the operator chose that track.
|
|
536
|
+
|
|
537
|
+
Whichever way it arrives, the consent screen warns over a release that is not
|
|
538
|
+
stable, and the install records its channel in the provenance record as the
|
|
539
|
+
TRACK the extension follows (`channel`, absent meaning stable). The update
|
|
540
|
+
check asks about each extension on its own track — one request per track in
|
|
541
|
+
use, side by side — and offers only what is newer than what is installed, so an extension installed at `0.1` unstable is updated to `1.0` stable,
|
|
542
|
+
and then to `1.0.1` unstable. The track only widens on an install: a stable
|
|
543
|
+
release landing over an unstable install leaves it on unstable. A pre-load
|
|
544
|
+
counts as the catalog's own here. The row and the details show the track, and
|
|
545
|
+
the details' **Follow stable releases only** puts it back on stable; an
|
|
546
|
+
uninstall or a file install clears it with the rest of the record.
|
|
547
|
+
|
|
499
548
|
At most once every six hours — on launch and on resume — the app asks the
|
|
500
549
|
catalog whether anything installed has a newer release, and says so in the
|
|
501
550
|
status bar and on the row; the notice comes down with the last update taken.
|
|
@@ -541,8 +590,8 @@ consent screen, the hash check. What differs is only where the bytes are kept
|
|
|
541
590
|
page are newer and patchier on Safari.
|
|
542
591
|
|
|
543
592
|
So the web build installs from a file, installs and updates from the catalog,
|
|
544
|
-
pre-loads the shipped bundles and
|
|
545
|
-
do.
|
|
593
|
+
pre-loads the shipped bundles and moves off the retired built-ins exactly as
|
|
594
|
+
the native ones do.
|
|
546
595
|
|
|
547
596
|
### When the store is lost
|
|
548
597
|
|
package/docs/hooks.md
CHANGED
|
@@ -162,7 +162,8 @@ describes it whenever it was last worked. It still labels a carried-forward
|
|
|
162
162
|
field by where it came from (`Call History` for a typed one, `Call History
|
|
163
163
|
(guess)` for a guessed one), and a station's whole location travels in one
|
|
164
164
|
of the two so the merge's grid/city reconciliation cannot split it — see
|
|
165
|
-
`
|
|
165
|
+
`ham2k-call-history`'s `src/index.ts` in
|
|
166
|
+
[ham2k/extensions](https://github.com/ham2k/extensions).
|
|
166
167
|
|
|
167
168
|
## ✅ `recentContextLookup` — live-context lookups
|
|
168
169
|
|
|
@@ -833,7 +834,13 @@ Field Day station sent, a satellite's downlink band). The exception is what
|
|
|
833
834
|
identifies the contact — `CALL`, `QSO_DATE`, `TIME_ON`, `QSO_DATE_OFF`,
|
|
834
835
|
`TIME_OFF`, `BAND`, `FREQ`, `MODE`, `SUBMODE`, `STATION_CALLSIGN`,
|
|
835
836
|
`OPERATOR`: a hook answering one of those is dropped, since it would rewrite
|
|
836
|
-
every record it is asked about, the whole-log backup included.
|
|
837
|
+
every record it is asked about, the whole-log backup included. A program whose
|
|
838
|
+
uploader needs `OPERATOR` on every record asks for it on the export instead,
|
|
839
|
+
`ExportRequest.operatorFallback` (`operatorFallback` on `activityExportHook`'s
|
|
840
|
+
and `huntingExportHook`'s rules): the core writes `OPERATOR` only where it says
|
|
841
|
+
something `STATION_CALLSIGN` doesn't, and with `'base'` or `'station'` it
|
|
842
|
+
writes the contact's own operator on every record, falling back to the station
|
|
843
|
+
call's base call or the station call itself. This is a
|
|
837
844
|
deliberate divergence from app-polo, whose contact fields come first and whose
|
|
838
845
|
main handler's are appended unchecked, so a name both answer is written twice. The **full export**, which asks every
|
|
839
846
|
program on the contact, has no such order: hooks answering `fieldCombinationsForOneQSO` are asked as one
|
|
@@ -1409,6 +1416,26 @@ report — as a real CaBo card; the host writes the title, body and kind, the
|
|
|
1409
1416
|
transport adds who/where/what-build and owns the account gate.
|
|
1410
1417
|
Implemented by: `ham2k-lofi`.
|
|
1411
1418
|
|
|
1419
|
+
`uploadStashFile` / `listStashFiles` / `emailStashLink`
|
|
1420
|
+
(optional) back the Exports pane's Stash button, which sends export files
|
|
1421
|
+
to a store the operator fetches from any browser (Ham2K's File Stash, at
|
|
1422
|
+
stash.ham2k.net). `uploadStashFile` takes ONE file and the `groupUuid` of
|
|
1423
|
+
the batch it belongs to — the host mints it, since the sandbox has no uuid
|
|
1424
|
+
source, and calls once per file: its call timeout does not stop a hook, so a
|
|
1425
|
+
whole batch in one call would go on uploading unseen after the host had
|
|
1426
|
+
told the operator it failed. It answers `{ uploaded: true }` or `{ error }`,
|
|
1427
|
+
never a throw, with `fatal: true` on any failure but the server refusing
|
|
1428
|
+
that one file — sync off, no account or permission, no answer — so the host
|
|
1429
|
+
stops instead of having every remaining file fail the same way. No answer
|
|
1430
|
+
says the file may have arrived: a body can reach the server after the reply
|
|
1431
|
+
is lost.
|
|
1432
|
+
`listStashFiles` answers the server's `{ stash_files: [...] }`, newest
|
|
1433
|
+
first, which the host reads to warn a free account before a new stash
|
|
1434
|
+
replaces one not yet fetched; `emailStashLink` mails the account's own
|
|
1435
|
+
address a link to its stash. Both answer `{ error }` for a refusal. All
|
|
1436
|
+
three own the same sync gate `submitReport` does.
|
|
1437
|
+
Implemented by: `ham2k-lofi`.
|
|
1438
|
+
|
|
1412
1439
|
## ✅ `panel` — panes an extension contributes to a view
|
|
1413
1440
|
|
|
1414
1441
|
A panel is a PANE the operator can put in a customizable view's layout
|
package/docs/settings.md
CHANGED
|
@@ -521,5 +521,5 @@ standalone dialog — not inline in a `settingsPanel`'s `FormRenderer`.
|
|
|
521
521
|
|
|
522
522
|
A `settingsPanel` field can reference an account for display/navigation via
|
|
523
523
|
`fieldType: 'account'` (see above), but the account itself is still fully
|
|
524
|
-
owned by its `AccountHook` registration. `
|
|
524
|
+
owned by its `AccountHook` registration. `ham2k-qrz` continues
|
|
525
525
|
to register under `account` — there is no migration to do.
|
package/package.json
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
// band, one contact per station per band. What this sample leaves out is
|
|
18
18
|
// everything around them — Cabrillo export, the ADIF exchange fields, the
|
|
19
19
|
// country-file zone suggestion, and writing a guessed exchange onto the QSO.
|
|
20
|
-
// See `docs/hooks.md` §`scoring`, and `
|
|
21
|
-
//
|
|
20
|
+
// See `docs/hooks.md` §`scoring`, and the catalog's `ham2k-cqww` for the whole
|
|
21
|
+
// thing.
|
|
22
22
|
|
|
23
23
|
import { annotateCallAgainstCountryFile, contestArithmetic, contestScorer, contestSummary, defineExtension } from "@ham2k/extension-sdk"
|
|
24
24
|
import type { ContestScorer, HookContext, JSONValue, LoggingControlDescriptor, Ref } from "@ham2k/extension-sdk"
|