@innspel/core 0.1.0 → 0.2.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/index.d.cts CHANGED
@@ -150,6 +150,24 @@ type ProgramsTaskCompletionView = {
150
150
  * Utfallet av én oppgave, valgt av testeren. Tre lukkede valg, aldri skala (rapport 4).
151
151
  */
152
152
  type ProgramsTaskOutcome = 'fungerte' | 'delvis' | 'ikke';
153
+ /**
154
+ * Tillatte bildetyper. Aldri SVG eller GIF (K9). Sjekkes på magic bytes ved mottak, ikke bare på oppgitt verdi.
155
+ */
156
+ type SubmissionsAttachmentMime = 'image/png' | 'image/jpeg' | 'image/webp';
157
+ /**
158
+ * Vedleggets tilstand etter complete. Verifiseringen (magic bytes, dimensjon, re-enkoding, EXIF-strip) kjører asynkront i isolert prosess; klienten trenger ikke vente.
159
+ */
160
+ type SubmissionsAttachmentView = {
161
+ id: string;
162
+ submissionId: string;
163
+ mime: SubmissionsAttachmentMime;
164
+ bytes: number;
165
+ /**
166
+ * Satt når vedlegget er verifisert og re-enkodet. Utelatt = under behandling.
167
+ */
168
+ processedAt?: string;
169
+ createdAt: string;
170
+ };
153
171
  /**
154
172
  * Minimal peker til en kjent feil, for visning i innsenderens liste.
155
173
  */
@@ -341,9 +359,47 @@ interface ResolvedSettings {
341
359
  minTextLength: number;
342
360
  screenshotsEnabled: boolean;
343
361
  }
362
+ /**
363
+ * Bildet slik verten gir det fra seg — Blob (React Native og nettleser),
364
+ * ArrayBuffer eller en typed array. Kjernen leser bare lengden; innholdet går
365
+ * rett til lagringen.
366
+ */
367
+ type AttachmentBody = Blob | ArrayBuffer | ArrayBufferView<ArrayBuffer>;
368
+ /**
369
+ * Ett skjermbilde, MASKERT på klienten før dette (K4, DD-39, DD-40). Kjernen
370
+ * sjekker grensene (PNG/JPEG/WebP, ≤ 5 MB, ≤ 4096², K9) og kaster med feltnavn
371
+ * før noe går på nettet; plattformen sjekker dem igjen før dekoding.
372
+ */
373
+ interface AttachmentInput {
374
+ mime: SubmissionsAttachmentMime;
375
+ body: AttachmentBody;
376
+ width: number;
377
+ height: number;
378
+ }
379
+ /**
380
+ * Hva som skjedde med vedlegget etter at teksten var lagret (DD-42).
381
+ * `failed` er ikke en feil for innsendingen — saken står, uten bilde — men
382
+ * UI-et skal si det: «Sendt — skjermbildet kunne ikke legges ved».
383
+ */
384
+ type AttachmentOutcome = {
385
+ status: 'uploaded';
386
+ attachment: SubmissionsAttachmentView;
387
+ } | {
388
+ status: 'failed';
389
+ error: Error;
390
+ }
391
+ /** Teksten ble køet offline; et skjermbilde køes aldri (DD-41), så det ble forkastet. */
392
+ | {
393
+ status: 'dropped';
394
+ };
344
395
  /** Felles felter for begge spor. */
345
396
  interface CaptureBase {
346
397
  text: string;
398
+ /**
399
+ * Skjermbilde, maskert på klienten. Sendes ETTER teksten, i egen forespørsel,
400
+ * og kan feile alene (DD-42) — se `CaptureResult.attachment`.
401
+ */
402
+ attachment?: AttachmentInput;
347
403
  /** Legges på konteksten fra `init` — typisk `screen`. */
348
404
  context?: Partial<ContextInput>;
349
405
  /**
@@ -371,9 +427,13 @@ type CaptureInput = CaptureFeil | CaptureOnske;
371
427
  type CaptureResult = {
372
428
  status: 'sent';
373
429
  submission: SubmissionsSubmissionStatusView;
430
+ attachment?: AttachmentOutcome;
374
431
  } | {
375
432
  status: 'queued';
376
433
  clientRef: string;
434
+ attachment?: {
435
+ status: 'dropped';
436
+ };
377
437
  };
378
438
  /** En rad i offline-køen. Kun tekst — et skjermbilde køes aldri (rapport 3, kap. 5). */
379
439
  interface QueuedSubmission {
@@ -457,6 +517,16 @@ interface InnspelClient {
457
517
  readonly programs: {
458
518
  list(): Promise<ProgramsMyPrograms>;
459
519
  };
520
+ readonly attachments: {
521
+ /**
522
+ * Laster opp ett maskert skjermbilde til en innsending som alt er lagret:
523
+ * presign → PUT til lagringen → complete. Bruk `capture({ attachment })` i
524
+ * stedet når teksten og bildet sendes sammen — da håndteres DD-42 for deg.
525
+ * Plattformen verifiserer og re-enkoder etterpå; `processedAt` er satt når
526
+ * det er gjort.
527
+ */
528
+ upload(submissionId: string, attachment: AttachmentInput): Promise<SubmissionsAttachmentView>;
529
+ };
460
530
  readonly queue: {
461
531
  pending(): Promise<QueuedSubmission[]>;
462
532
  /** Sendes ellers automatisk når widgeten åpnes. Aldri ved oppstart (DD-41). */
@@ -565,6 +635,86 @@ declare class InnspelQueueFullError extends Error {
565
635
  */
566
636
  declare const KONTEKST_FELTER: readonly ["platform", "appVersion", "build", "os", "screen", "locale"];
567
637
 
638
+ /**
639
+ * Grensene for et vedlegg (K9). Tallene er kontraktens
640
+ * (`AttachmentPresignRequest`: 5 MB, 4096²) og sjekkes her FØR noe går på
641
+ * nettet — plattformen sjekker dem på nytt før dekoding.
642
+ */
643
+ declare const VEDLEGG_GRENSER: {
644
+ readonly maxBytes: 5242880;
645
+ readonly maxDimension: 4096;
646
+ };
647
+ /** Allowlisten (K9): aldri SVG, aldri GIF. Avgjøres på ledningen av magic bytes, ikke av denne strengen. */
648
+ declare const TILLATTE_MIME: readonly SubmissionsAttachmentMime[];
649
+
650
+ /**
651
+ * Geometrien bak maskeringen (DD-39, DD-40) — ren TypeScript uten UI, så den
652
+ * kan testes her og brukes likt av `@innspel/react-native` og `@innspel/web`.
653
+ *
654
+ * Alt er rektangler i BILDETS pikselkoordinater. UI-laget tegner dem som
655
+ * heldekkende flater over forhåndsvisningen og rekomponerer bitmapen; det som
656
+ * står her avgjør bare HVOR flatene ligger. Private views (DD-39) er rektangler
657
+ * som UI-et aldri lar brukeren fjerne — den regelen håndheves der, ikke her,
658
+ * fordi den handler om hvem som la rektangelet, ikke om geometri.
659
+ */
660
+ interface MaskPoint {
661
+ x: number;
662
+ y: number;
663
+ }
664
+ interface MaskRect {
665
+ x: number;
666
+ y: number;
667
+ width: number;
668
+ height: number;
669
+ }
670
+ /**
671
+ * Ett fingerstrøk («Tegn over», DD-40) til rektangler: en kvadratisk flate per
672
+ * punkt, og én per mellomrom mellom to punkter, så strøket er tett selv om
673
+ * fingeren beveget seg fort. Alle klippes til bildet.
674
+ */
675
+ declare function strokTilRekter(punkter: readonly MaskPoint[], penselBredde: number, bilde: {
676
+ width: number;
677
+ height: number;
678
+ }): MaskRect[];
679
+ /** «Masker felt» (DD-40): ett trykk legger en boks sentrert på punktet. */
680
+ declare function trykkTilRekt(punkt: MaskPoint, boks: {
681
+ width: number;
682
+ height: number;
683
+ }, bilde: {
684
+ width: number;
685
+ height: number;
686
+ }): MaskRect | null;
687
+ /**
688
+ * Klipper et rektangel til bildet. Null når ingenting er igjen — et rektangel
689
+ * helt utenfor bildet skal ikke tegnes, og heller ikke telles som en maskering.
690
+ */
691
+ declare function klipp(r: MaskRect, bilde: {
692
+ width: number;
693
+ height: number;
694
+ }): MaskRect | null;
695
+ /**
696
+ * Skalerer et rektangel fra ett koordinatsystem til et annet — typisk fra
697
+ * skjermpunkter (der fingeren var) til bildepiksler (der masken skal ligge).
698
+ * Rundes UTOVER: en maske skal aldri bli en piksel for liten.
699
+ */
700
+ declare function skaler(r: MaskRect, fra: {
701
+ width: number;
702
+ height: number;
703
+ }, til: {
704
+ width: number;
705
+ height: number;
706
+ }): MaskRect;
707
+ /** Om et punkt ligger i et av rektanglene — brukes for «trykk på en maske for å angre den». */
708
+ declare function treffer(punkt: MaskPoint, r: MaskRect): boolean;
709
+ /**
710
+ * Om rektanglene dekker hele bildet. UI-et bruker det til å si «alt er
711
+ * maskert» — da er det ingenting å sende, og «Fjern bildet» er det ærlige valget.
712
+ */
713
+ declare function dekkerAlt(rekter: readonly MaskRect[], bilde: {
714
+ width: number;
715
+ height: number;
716
+ }): boolean;
717
+
568
718
  /**
569
719
  * Versjonen som sendes som `context.sdkVersion` på hver innsending.
570
720
  *
@@ -574,6 +724,6 @@ declare const KONTEKST_FELTER: readonly ["platform", "appVersion", "build", "os"
574
724
  * ikke kan lese JSON, og `test/versjon.test.ts` holder den i takt med
575
725
  * `package.json`.
576
726
  */
577
- declare const VERSJON = "0.1.0";
727
+ declare const VERSJON = "0.2.0";
578
728
 
579
- export { type CaptureFeil, type CaptureInput, type CaptureOnske, type CaptureResult, type ContextInput, type CommonCorrelation as Correlation, type CommonErrorCode as ErrorCode, type FlushResult, type IdentityProvider, InnspelApiError, type InnspelClient, InnspelConfigError, type InnspelOptions, InnspelQueueFullError, type InnspelStorage, KONTEKST_FELTER, KO_TAK, type KnownIssuesKnownIssueCard as KnownIssueCard, type KnownIssueQuery, type SubmissionsKnownIssueRef as KnownIssueRef, type KnownIssuesKnownIssueStatus as KnownIssueStatus, type ProgramsMyPrograms as MyPrograms, type MySubmission, type OpenFeedbackOptions, type PatternFinding, type PatternKind, type CommonPlatform as Platform, type Presenter, type QueuedSubmission, type SubmissionsReasonCode as ReasonCode, type SubmissionsReplyView as ReplyView, type ResolvedSettings, type SubmissionsStatusType as StatusType, type SubmissionsSubmissionStatus as SubmissionStatus, type SubmissionsSubmissionStatusView as SubmissionStatusView, type SubmissionsSubmissionType as SubmissionType, type Telemetry, type TelemetryEvent, type TextMaxLength, VERSJON, init, scanText };
729
+ export { type AttachmentBody, type AttachmentInput, type SubmissionsAttachmentMime as AttachmentMime, type AttachmentOutcome, type SubmissionsAttachmentView as AttachmentView, type CaptureFeil, type CaptureInput, type CaptureOnske, type CaptureResult, type ContextInput, type CommonCorrelation as Correlation, type CommonErrorCode as ErrorCode, type FlushResult, type IdentityProvider, InnspelApiError, type InnspelClient, InnspelConfigError, type InnspelOptions, InnspelQueueFullError, type InnspelStorage, KONTEKST_FELTER, KO_TAK, type KnownIssuesKnownIssueCard as KnownIssueCard, type KnownIssueQuery, type SubmissionsKnownIssueRef as KnownIssueRef, type KnownIssuesKnownIssueStatus as KnownIssueStatus, type MaskPoint, type MaskRect, type ProgramsMyPrograms as MyPrograms, type MySubmission, type OpenFeedbackOptions, type PatternFinding, type PatternKind, type CommonPlatform as Platform, type Presenter, type QueuedSubmission, type SubmissionsReasonCode as ReasonCode, type SubmissionsReplyView as ReplyView, type ResolvedSettings, type SubmissionsStatusType as StatusType, type SubmissionsSubmissionStatus as SubmissionStatus, type SubmissionsSubmissionStatusView as SubmissionStatusView, type SubmissionsSubmissionType as SubmissionType, TILLATTE_MIME, type Telemetry, type TelemetryEvent, type TextMaxLength, VEDLEGG_GRENSER, VERSJON, dekkerAlt, init, klipp, scanText, skaler, strokTilRekter, treffer, trykkTilRekt };
package/dist/index.d.ts CHANGED
@@ -150,6 +150,24 @@ type ProgramsTaskCompletionView = {
150
150
  * Utfallet av én oppgave, valgt av testeren. Tre lukkede valg, aldri skala (rapport 4).
151
151
  */
152
152
  type ProgramsTaskOutcome = 'fungerte' | 'delvis' | 'ikke';
153
+ /**
154
+ * Tillatte bildetyper. Aldri SVG eller GIF (K9). Sjekkes på magic bytes ved mottak, ikke bare på oppgitt verdi.
155
+ */
156
+ type SubmissionsAttachmentMime = 'image/png' | 'image/jpeg' | 'image/webp';
157
+ /**
158
+ * Vedleggets tilstand etter complete. Verifiseringen (magic bytes, dimensjon, re-enkoding, EXIF-strip) kjører asynkront i isolert prosess; klienten trenger ikke vente.
159
+ */
160
+ type SubmissionsAttachmentView = {
161
+ id: string;
162
+ submissionId: string;
163
+ mime: SubmissionsAttachmentMime;
164
+ bytes: number;
165
+ /**
166
+ * Satt når vedlegget er verifisert og re-enkodet. Utelatt = under behandling.
167
+ */
168
+ processedAt?: string;
169
+ createdAt: string;
170
+ };
153
171
  /**
154
172
  * Minimal peker til en kjent feil, for visning i innsenderens liste.
155
173
  */
@@ -341,9 +359,47 @@ interface ResolvedSettings {
341
359
  minTextLength: number;
342
360
  screenshotsEnabled: boolean;
343
361
  }
362
+ /**
363
+ * Bildet slik verten gir det fra seg — Blob (React Native og nettleser),
364
+ * ArrayBuffer eller en typed array. Kjernen leser bare lengden; innholdet går
365
+ * rett til lagringen.
366
+ */
367
+ type AttachmentBody = Blob | ArrayBuffer | ArrayBufferView<ArrayBuffer>;
368
+ /**
369
+ * Ett skjermbilde, MASKERT på klienten før dette (K4, DD-39, DD-40). Kjernen
370
+ * sjekker grensene (PNG/JPEG/WebP, ≤ 5 MB, ≤ 4096², K9) og kaster med feltnavn
371
+ * før noe går på nettet; plattformen sjekker dem igjen før dekoding.
372
+ */
373
+ interface AttachmentInput {
374
+ mime: SubmissionsAttachmentMime;
375
+ body: AttachmentBody;
376
+ width: number;
377
+ height: number;
378
+ }
379
+ /**
380
+ * Hva som skjedde med vedlegget etter at teksten var lagret (DD-42).
381
+ * `failed` er ikke en feil for innsendingen — saken står, uten bilde — men
382
+ * UI-et skal si det: «Sendt — skjermbildet kunne ikke legges ved».
383
+ */
384
+ type AttachmentOutcome = {
385
+ status: 'uploaded';
386
+ attachment: SubmissionsAttachmentView;
387
+ } | {
388
+ status: 'failed';
389
+ error: Error;
390
+ }
391
+ /** Teksten ble køet offline; et skjermbilde køes aldri (DD-41), så det ble forkastet. */
392
+ | {
393
+ status: 'dropped';
394
+ };
344
395
  /** Felles felter for begge spor. */
345
396
  interface CaptureBase {
346
397
  text: string;
398
+ /**
399
+ * Skjermbilde, maskert på klienten. Sendes ETTER teksten, i egen forespørsel,
400
+ * og kan feile alene (DD-42) — se `CaptureResult.attachment`.
401
+ */
402
+ attachment?: AttachmentInput;
347
403
  /** Legges på konteksten fra `init` — typisk `screen`. */
348
404
  context?: Partial<ContextInput>;
349
405
  /**
@@ -371,9 +427,13 @@ type CaptureInput = CaptureFeil | CaptureOnske;
371
427
  type CaptureResult = {
372
428
  status: 'sent';
373
429
  submission: SubmissionsSubmissionStatusView;
430
+ attachment?: AttachmentOutcome;
374
431
  } | {
375
432
  status: 'queued';
376
433
  clientRef: string;
434
+ attachment?: {
435
+ status: 'dropped';
436
+ };
377
437
  };
378
438
  /** En rad i offline-køen. Kun tekst — et skjermbilde køes aldri (rapport 3, kap. 5). */
379
439
  interface QueuedSubmission {
@@ -457,6 +517,16 @@ interface InnspelClient {
457
517
  readonly programs: {
458
518
  list(): Promise<ProgramsMyPrograms>;
459
519
  };
520
+ readonly attachments: {
521
+ /**
522
+ * Laster opp ett maskert skjermbilde til en innsending som alt er lagret:
523
+ * presign → PUT til lagringen → complete. Bruk `capture({ attachment })` i
524
+ * stedet når teksten og bildet sendes sammen — da håndteres DD-42 for deg.
525
+ * Plattformen verifiserer og re-enkoder etterpå; `processedAt` er satt når
526
+ * det er gjort.
527
+ */
528
+ upload(submissionId: string, attachment: AttachmentInput): Promise<SubmissionsAttachmentView>;
529
+ };
460
530
  readonly queue: {
461
531
  pending(): Promise<QueuedSubmission[]>;
462
532
  /** Sendes ellers automatisk når widgeten åpnes. Aldri ved oppstart (DD-41). */
@@ -565,6 +635,86 @@ declare class InnspelQueueFullError extends Error {
565
635
  */
566
636
  declare const KONTEKST_FELTER: readonly ["platform", "appVersion", "build", "os", "screen", "locale"];
567
637
 
638
+ /**
639
+ * Grensene for et vedlegg (K9). Tallene er kontraktens
640
+ * (`AttachmentPresignRequest`: 5 MB, 4096²) og sjekkes her FØR noe går på
641
+ * nettet — plattformen sjekker dem på nytt før dekoding.
642
+ */
643
+ declare const VEDLEGG_GRENSER: {
644
+ readonly maxBytes: 5242880;
645
+ readonly maxDimension: 4096;
646
+ };
647
+ /** Allowlisten (K9): aldri SVG, aldri GIF. Avgjøres på ledningen av magic bytes, ikke av denne strengen. */
648
+ declare const TILLATTE_MIME: readonly SubmissionsAttachmentMime[];
649
+
650
+ /**
651
+ * Geometrien bak maskeringen (DD-39, DD-40) — ren TypeScript uten UI, så den
652
+ * kan testes her og brukes likt av `@innspel/react-native` og `@innspel/web`.
653
+ *
654
+ * Alt er rektangler i BILDETS pikselkoordinater. UI-laget tegner dem som
655
+ * heldekkende flater over forhåndsvisningen og rekomponerer bitmapen; det som
656
+ * står her avgjør bare HVOR flatene ligger. Private views (DD-39) er rektangler
657
+ * som UI-et aldri lar brukeren fjerne — den regelen håndheves der, ikke her,
658
+ * fordi den handler om hvem som la rektangelet, ikke om geometri.
659
+ */
660
+ interface MaskPoint {
661
+ x: number;
662
+ y: number;
663
+ }
664
+ interface MaskRect {
665
+ x: number;
666
+ y: number;
667
+ width: number;
668
+ height: number;
669
+ }
670
+ /**
671
+ * Ett fingerstrøk («Tegn over», DD-40) til rektangler: en kvadratisk flate per
672
+ * punkt, og én per mellomrom mellom to punkter, så strøket er tett selv om
673
+ * fingeren beveget seg fort. Alle klippes til bildet.
674
+ */
675
+ declare function strokTilRekter(punkter: readonly MaskPoint[], penselBredde: number, bilde: {
676
+ width: number;
677
+ height: number;
678
+ }): MaskRect[];
679
+ /** «Masker felt» (DD-40): ett trykk legger en boks sentrert på punktet. */
680
+ declare function trykkTilRekt(punkt: MaskPoint, boks: {
681
+ width: number;
682
+ height: number;
683
+ }, bilde: {
684
+ width: number;
685
+ height: number;
686
+ }): MaskRect | null;
687
+ /**
688
+ * Klipper et rektangel til bildet. Null når ingenting er igjen — et rektangel
689
+ * helt utenfor bildet skal ikke tegnes, og heller ikke telles som en maskering.
690
+ */
691
+ declare function klipp(r: MaskRect, bilde: {
692
+ width: number;
693
+ height: number;
694
+ }): MaskRect | null;
695
+ /**
696
+ * Skalerer et rektangel fra ett koordinatsystem til et annet — typisk fra
697
+ * skjermpunkter (der fingeren var) til bildepiksler (der masken skal ligge).
698
+ * Rundes UTOVER: en maske skal aldri bli en piksel for liten.
699
+ */
700
+ declare function skaler(r: MaskRect, fra: {
701
+ width: number;
702
+ height: number;
703
+ }, til: {
704
+ width: number;
705
+ height: number;
706
+ }): MaskRect;
707
+ /** Om et punkt ligger i et av rektanglene — brukes for «trykk på en maske for å angre den». */
708
+ declare function treffer(punkt: MaskPoint, r: MaskRect): boolean;
709
+ /**
710
+ * Om rektanglene dekker hele bildet. UI-et bruker det til å si «alt er
711
+ * maskert» — da er det ingenting å sende, og «Fjern bildet» er det ærlige valget.
712
+ */
713
+ declare function dekkerAlt(rekter: readonly MaskRect[], bilde: {
714
+ width: number;
715
+ height: number;
716
+ }): boolean;
717
+
568
718
  /**
569
719
  * Versjonen som sendes som `context.sdkVersion` på hver innsending.
570
720
  *
@@ -574,6 +724,6 @@ declare const KONTEKST_FELTER: readonly ["platform", "appVersion", "build", "os"
574
724
  * ikke kan lese JSON, og `test/versjon.test.ts` holder den i takt med
575
725
  * `package.json`.
576
726
  */
577
- declare const VERSJON = "0.1.0";
727
+ declare const VERSJON = "0.2.0";
578
728
 
579
- export { type CaptureFeil, type CaptureInput, type CaptureOnske, type CaptureResult, type ContextInput, type CommonCorrelation as Correlation, type CommonErrorCode as ErrorCode, type FlushResult, type IdentityProvider, InnspelApiError, type InnspelClient, InnspelConfigError, type InnspelOptions, InnspelQueueFullError, type InnspelStorage, KONTEKST_FELTER, KO_TAK, type KnownIssuesKnownIssueCard as KnownIssueCard, type KnownIssueQuery, type SubmissionsKnownIssueRef as KnownIssueRef, type KnownIssuesKnownIssueStatus as KnownIssueStatus, type ProgramsMyPrograms as MyPrograms, type MySubmission, type OpenFeedbackOptions, type PatternFinding, type PatternKind, type CommonPlatform as Platform, type Presenter, type QueuedSubmission, type SubmissionsReasonCode as ReasonCode, type SubmissionsReplyView as ReplyView, type ResolvedSettings, type SubmissionsStatusType as StatusType, type SubmissionsSubmissionStatus as SubmissionStatus, type SubmissionsSubmissionStatusView as SubmissionStatusView, type SubmissionsSubmissionType as SubmissionType, type Telemetry, type TelemetryEvent, type TextMaxLength, VERSJON, init, scanText };
729
+ export { type AttachmentBody, type AttachmentInput, type SubmissionsAttachmentMime as AttachmentMime, type AttachmentOutcome, type SubmissionsAttachmentView as AttachmentView, type CaptureFeil, type CaptureInput, type CaptureOnske, type CaptureResult, type ContextInput, type CommonCorrelation as Correlation, type CommonErrorCode as ErrorCode, type FlushResult, type IdentityProvider, InnspelApiError, type InnspelClient, InnspelConfigError, type InnspelOptions, InnspelQueueFullError, type InnspelStorage, KONTEKST_FELTER, KO_TAK, type KnownIssuesKnownIssueCard as KnownIssueCard, type KnownIssueQuery, type SubmissionsKnownIssueRef as KnownIssueRef, type KnownIssuesKnownIssueStatus as KnownIssueStatus, type MaskPoint, type MaskRect, type ProgramsMyPrograms as MyPrograms, type MySubmission, type OpenFeedbackOptions, type PatternFinding, type PatternKind, type CommonPlatform as Platform, type Presenter, type QueuedSubmission, type SubmissionsReasonCode as ReasonCode, type SubmissionsReplyView as ReplyView, type ResolvedSettings, type SubmissionsStatusType as StatusType, type SubmissionsSubmissionStatus as SubmissionStatus, type SubmissionsSubmissionStatusView as SubmissionStatusView, type SubmissionsSubmissionType as SubmissionType, TILLATTE_MIME, type Telemetry, type TelemetryEvent, type TextMaxLength, VEDLEGG_GRENSER, VERSJON, dekkerAlt, init, klipp, scanText, skaler, strokTilRekter, treffer, trykkTilRekt };
package/dist/index.js CHANGED
@@ -778,6 +778,28 @@ var createClient = (config = {}) => {
778
778
  var client = createClient(createConfig());
779
779
 
780
780
  // src/generated/api/sdk.gen.ts
781
+ var attachmentsPresign = (options) => (options.client ?? client).post({
782
+ security: [{ name: "X-Innspel-Tenant-Key", type: "apiKey" }, {
783
+ key: "FeedbackTokenAuth",
784
+ scheme: "bearer",
785
+ type: "http"
786
+ }],
787
+ url: "/v1/ingest/attachments/presign",
788
+ ...options,
789
+ headers: {
790
+ "Content-Type": "application/json",
791
+ ...options.headers
792
+ }
793
+ });
794
+ var attachmentsComplete = (options) => (options.client ?? client).post({
795
+ security: [{ name: "X-Innspel-Tenant-Key", type: "apiKey" }, {
796
+ key: "FeedbackTokenAuth",
797
+ scheme: "bearer",
798
+ type: "http"
799
+ }],
800
+ url: "/v1/ingest/attachments/{id}/complete",
801
+ ...options
802
+ });
781
803
  var knownIssuesList2 = (options) => (options?.client ?? client).get({
782
804
  security: [{ name: "X-Innspel-Tenant-Key", type: "apiKey" }, {
783
805
  key: "FeedbackTokenAuth",
@@ -1127,8 +1149,66 @@ function nyClientRef() {
1127
1149
  return hex.join("");
1128
1150
  }
1129
1151
 
1152
+ // src/vedlegg.ts
1153
+ var VEDLEGG_GRENSER = {
1154
+ maxBytes: 5242880,
1155
+ maxDimension: 4096
1156
+ };
1157
+ var TILLATTE_MIME = ["image/png", "image/jpeg", "image/webp"];
1158
+ function byteLengde(body) {
1159
+ if (typeof body === "object" && body !== null && "size" in body && typeof body.size === "number") {
1160
+ return body.size;
1161
+ }
1162
+ return body.byteLength;
1163
+ }
1164
+ function validerVedlegg(input) {
1165
+ if (input === null || typeof input !== "object") {
1166
+ throw new InnspelConfigError("attachment", "m\xE5 v\xE6re et objekt med { submissionId?, mime, body, width, height }");
1167
+ }
1168
+ if (!TILLATTE_MIME.includes(input.mime)) {
1169
+ throw new InnspelConfigError(
1170
+ "attachment.mime",
1171
+ `m\xE5 v\xE6re en av ${TILLATTE_MIME.join(", ")}. SVG og GIF er aldri tillatt (K9)`
1172
+ );
1173
+ }
1174
+ if (input.body === null || input.body === void 0) {
1175
+ throw new InnspelConfigError("attachment.body", "mangler \u2014 send bildet som Blob, ArrayBuffer eller Uint8Array");
1176
+ }
1177
+ const bytes = byteLengde(input.body);
1178
+ if (!Number.isFinite(bytes) || bytes < 1) {
1179
+ throw new InnspelConfigError("attachment.body", "er tom");
1180
+ }
1181
+ if (bytes > VEDLEGG_GRENSER.maxBytes) {
1182
+ throw new InnspelConfigError(
1183
+ "attachment.body",
1184
+ `er ${bytes} byte, over taket p\xE5 ${VEDLEGG_GRENSER.maxBytes} (5 MB). Reduser kvaliteten eller oppl\xF8sningen ved fangst`
1185
+ );
1186
+ }
1187
+ for (const felt of ["width", "height"]) {
1188
+ const v = input[felt];
1189
+ if (!Number.isInteger(v) || v < 1 || v > VEDLEGG_GRENSER.maxDimension) {
1190
+ throw new InnspelConfigError(
1191
+ `attachment.${felt}`,
1192
+ `m\xE5 v\xE6re et heltall mellom 1 og ${VEDLEGG_GRENSER.maxDimension} (K9)`
1193
+ );
1194
+ }
1195
+ }
1196
+ }
1197
+ async function lastOppTilLagring(fetchImpl, presign, body, signal) {
1198
+ const headers = {};
1199
+ for (const h of presign.headers) headers[h.name] = h.value;
1200
+ const res = await fetchImpl(new Request(presign.uploadUrl, { method: "PUT", headers, body, signal }));
1201
+ if (!res.ok) {
1202
+ throw new InnspelApiError(
1203
+ res.status,
1204
+ "unknown",
1205
+ `Lagringen avviste opplastingen med status ${res.status}. Bildet ble ikke lagt ved.`
1206
+ );
1207
+ }
1208
+ }
1209
+
1130
1210
  // src/versjon.ts
1131
- var VERSJON = "0.1.0";
1211
+ var VERSJON = "0.2.0";
1132
1212
 
1133
1213
  // src/klient.ts
1134
1214
  var STANDARD_TIMEOUT_MS = 1e4;
@@ -1270,6 +1350,31 @@ function init(options) {
1270
1350
  })
1271
1351
  );
1272
1352
  }
1353
+ async function lastOppVedlegg(submissionId, vedlegg) {
1354
+ const presign = await medToken(
1355
+ (h) => attachmentsPresign({
1356
+ client: klient,
1357
+ headers: h,
1358
+ signal: signal(),
1359
+ body: {
1360
+ submissionId,
1361
+ mime: vedlegg.mime,
1362
+ bytes: byteLengde(vedlegg.body),
1363
+ width: vedlegg.width,
1364
+ height: vedlegg.height
1365
+ }
1366
+ })
1367
+ );
1368
+ await lastOppTilLagring(options.fetch ?? fetch, presign, vedlegg.body, signal());
1369
+ return medToken(
1370
+ (h) => attachmentsComplete({
1371
+ client: klient,
1372
+ headers: h,
1373
+ signal: signal(),
1374
+ path: { id: presign.attachmentId }
1375
+ })
1376
+ );
1377
+ }
1273
1378
  const flushK\u00F8 = () => flushKo(
1274
1379
  k\u00F8,
1275
1380
  (rad) => sendN\u00E5({ type: rad.type, text: rad.text }, rad.clientRef, kontekst),
@@ -1295,17 +1400,12 @@ function init(options) {
1295
1400
  );
1296
1401
  }
1297
1402
  if (input.context) validerKontekst(input.context, "capture.context", false);
1403
+ if (input.attachment) validerVedlegg(input.attachment);
1298
1404
  const clientRef = input.clientRef ?? nyClientRef();
1299
1405
  const kontekstForKall = sl\u00E5SammenKontekst(options.context, input.context, VERSJON);
1406
+ let submission;
1300
1407
  try {
1301
- const submission = await sendN\u00E5(input, clientRef, kontekstForKall);
1302
- telemetri.emit({
1303
- name: "submission_sent",
1304
- type: input.type,
1305
- hasAttachment: false,
1306
- offlineQueued: false
1307
- });
1308
- return { status: "sent", submission };
1408
+ submission = await sendN\u00E5(input, clientRef, kontekstForKall);
1309
1409
  } catch (e) {
1310
1410
  if (e instanceof InnspelApiError) {
1311
1411
  telemetri.emit({ name: "submission_failed", reason: e.code });
@@ -1323,8 +1423,23 @@ function init(options) {
1323
1423
  hasAttachment: false,
1324
1424
  offlineQueued: true
1325
1425
  });
1326
- return { status: "queued", clientRef };
1426
+ return input.attachment ? { status: "queued", clientRef, attachment: { status: "dropped" } } : { status: "queued", clientRef };
1427
+ }
1428
+ let attachment;
1429
+ if (input.attachment) {
1430
+ try {
1431
+ attachment = { status: "uploaded", attachment: await lastOppVedlegg(submission.id, input.attachment) };
1432
+ } catch (e) {
1433
+ attachment = { status: "failed", error: e instanceof Error ? e : new Error(String(e)) };
1434
+ }
1327
1435
  }
1436
+ telemetri.emit({
1437
+ name: "submission_sent",
1438
+ type: input.type,
1439
+ hasAttachment: attachment?.status === "uploaded",
1440
+ offlineQueued: false
1441
+ });
1442
+ return attachment ? { status: "sent", submission, attachment } : { status: "sent", submission };
1328
1443
  },
1329
1444
  knownIssues: {
1330
1445
  async for(query = {}) {
@@ -1404,6 +1519,15 @@ function init(options) {
1404
1519
  );
1405
1520
  }
1406
1521
  },
1522
+ attachments: {
1523
+ async upload(submissionId, attachment) {
1524
+ if (typeof submissionId !== "string" || submissionId.length === 0) {
1525
+ throw new InnspelConfigError("submissionId", "mangler \u2014 bruk id fra innsendingen capture() ga");
1526
+ }
1527
+ validerVedlegg(attachment);
1528
+ return lastOppVedlegg(submissionId, attachment);
1529
+ }
1530
+ },
1407
1531
  queue: {
1408
1532
  pending: () => k\u00F8.les(),
1409
1533
  flush: flushK\u00F8
@@ -1427,6 +1551,57 @@ function init(options) {
1427
1551
  return innspel;
1428
1552
  }
1429
1553
 
1430
- export { InnspelApiError, InnspelConfigError, InnspelQueueFullError, KONTEKST_FELTER, KO_TAK, VERSJON, init, scanText };
1554
+ // src/maskering.ts
1555
+ function strokTilRekter(punkter, penselBredde, bilde) {
1556
+ if (penselBredde <= 0 || punkter.length === 0) return [];
1557
+ const halv = penselBredde / 2;
1558
+ const ut = [];
1559
+ const legg = (p) => {
1560
+ const r = klipp({ x: p.x - halv, y: p.y - halv, width: penselBredde, height: penselBredde }, bilde);
1561
+ if (r) ut.push(r);
1562
+ };
1563
+ legg(punkter[0]);
1564
+ for (let i = 1; i < punkter.length; i++) {
1565
+ const a = punkter[i - 1];
1566
+ const b = punkter[i];
1567
+ const avstand = Math.hypot(b.x - a.x, b.y - a.y);
1568
+ const steg = Math.max(1, Math.ceil(avstand / halv));
1569
+ for (let s = 1; s <= steg; s++) {
1570
+ legg({ x: a.x + (b.x - a.x) * s / steg, y: a.y + (b.y - a.y) * s / steg });
1571
+ }
1572
+ }
1573
+ return ut;
1574
+ }
1575
+ function trykkTilRekt(punkt, boks, bilde) {
1576
+ return klipp({ x: punkt.x - boks.width / 2, y: punkt.y - boks.height / 2, width: boks.width, height: boks.height }, bilde);
1577
+ }
1578
+ function klipp(r, bilde) {
1579
+ const x1 = Math.max(0, r.x);
1580
+ const y1 = Math.max(0, r.y);
1581
+ const x2 = Math.min(bilde.width, r.x + r.width);
1582
+ const y2 = Math.min(bilde.height, r.y + r.height);
1583
+ if (x2 <= x1 || y2 <= y1) return null;
1584
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
1585
+ }
1586
+ function skaler(r, fra, til) {
1587
+ const sx = til.width / fra.width;
1588
+ const sy = til.height / fra.height;
1589
+ const x = Math.floor(r.x * sx);
1590
+ const y = Math.floor(r.y * sy);
1591
+ return {
1592
+ x,
1593
+ y,
1594
+ width: Math.ceil((r.x + r.width) * sx) - x,
1595
+ height: Math.ceil((r.y + r.height) * sy) - y
1596
+ };
1597
+ }
1598
+ function treffer(punkt, r) {
1599
+ return punkt.x >= r.x && punkt.x < r.x + r.width && punkt.y >= r.y && punkt.y < r.y + r.height;
1600
+ }
1601
+ function dekkerAlt(rekter, bilde) {
1602
+ return rekter.some((r) => r.x <= 0 && r.y <= 0 && r.x + r.width >= bilde.width && r.y + r.height >= bilde.height);
1603
+ }
1604
+
1605
+ export { InnspelApiError, InnspelConfigError, InnspelQueueFullError, KONTEKST_FELTER, KO_TAK, TILLATTE_MIME, VEDLEGG_GRENSER, VERSJON, dekkerAlt, init, klipp, scanText, skaler, strokTilRekter, treffer, trykkTilRekt };
1431
1606
  //# sourceMappingURL=index.js.map
1432
1607
  //# sourceMappingURL=index.js.map