@qaiddev/thumbs-embed 1.1.1 → 1.2.1

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Lazy loader + launcher for `@qaiddev/quests-embed`.
3
+ *
4
+ * `thumbs-embed` links a button to a quest but owns no questionnaire code
5
+ * and takes no build-time dependency on the quests package. Instead the
6
+ * quests widget is imported from a CDN the first time a quest is triggered,
7
+ * so the shipped bundle stays zero-dependency and small.
8
+ *
9
+ * Only the tiny slice of the quests public API that we actually use is
10
+ * modelled here structurally, so a version bump of the quests package that
11
+ * keeps this surface stable needs no change in thumbs-embed.
12
+ */
13
+ /** Default ES-module URL, pinned to a compatible major on unpkg. */
14
+ export declare const DEFAULT_QUESTS_MODULE_URL = "https://unpkg.com/@qaiddev/quests-embed@1/dist/qaid-quests.js";
15
+ /** The bit of a QaidQuests instance we hold onto. */
16
+ export interface QuestInstance {
17
+ destroy(): void;
18
+ }
19
+ /** The bit of QaidQuests config we pass. Mirrors QuestsConfig loosely. */
20
+ export interface LaunchedQuestConfig {
21
+ endpoint: string;
22
+ configUrl: string;
23
+ apiKey?: string;
24
+ metadata?: Record<string, unknown>;
25
+ onComplete?: (answers: Record<string, unknown>) => void;
26
+ onClose?: () => void;
27
+ }
28
+ type QuestsConstructor = new (config: LaunchedQuestConfig) => QuestInstance;
29
+ /** Shape of the quests module's default/entry exports. */
30
+ export interface QuestsModule {
31
+ QaidQuests: QuestsConstructor;
32
+ }
33
+ type Importer = (url: string) => Promise<unknown>;
34
+ /**
35
+ * @internal Test seam: swap the dynamic importer and clear the cache.
36
+ * Pass `null` to restore the real dynamic `import()`. Not re-exported
37
+ * from the package entry, so it isn't public API.
38
+ */
39
+ export declare function _setQuestsImporter(fn: Importer | null): void;
40
+ /**
41
+ * Import the quests module, caching the promise per URL. A failed load
42
+ * clears the cache so a later trigger can retry (e.g. after a transient
43
+ * network error) rather than being stuck with a rejected promise.
44
+ */
45
+ export declare function loadQuestsModule(moduleUrl: string): Promise<QuestsModule>;
46
+ export interface LaunchQuestOptions {
47
+ /** Quest id to launch (the `[id]` in `{base}/{id}/definition`). */
48
+ questId: string;
49
+ /** Quest-service base URL, e.g. "https://qaid.dev/api/quests". */
50
+ base: string;
51
+ /** API key for the quest service (optional). */
52
+ apiKey?: string;
53
+ /** ES-module URL to load the quests widget from. */
54
+ moduleUrl: string;
55
+ /** Feedback record id to correlate the response with (optional). */
56
+ feedbackId?: string | number | null;
57
+ /** Called when the quest embed is torn down. */
58
+ onClose?: () => void;
59
+ }
60
+ /**
61
+ * Load the quests widget (if not already loaded) and open the given quest
62
+ * as its own centered modal. Rejects if the module can't be loaded — the
63
+ * caller should fall back to its normal UI.
64
+ */
65
+ export declare function launchQuest(opts: LaunchQuestOptions): Promise<QuestInstance>;
66
+ export {};
package/dist/types.d.ts CHANGED
@@ -99,6 +99,43 @@ export interface FeedbackConfig {
99
99
  /** Max height of the screenshot. Default: 800 */
100
100
  maxHeight?: number;
101
101
  };
102
+ /**
103
+ * Link buttons to quests. When a button has a quest id here, clicking it
104
+ * launches that quest (via `@qaiddev/quests-embed`, lazy-loaded at runtime)
105
+ * in place of the optional message box. Leave `base` unset to keep the
106
+ * classic message-box behaviour for every button.
107
+ */
108
+ quests?: QuestsLaunchConfig;
109
+ }
110
+ /**
111
+ * Per-button quest links plus how to reach the quest service.
112
+ *
113
+ * The quests widget is loaded on demand from a CDN the first time a quest
114
+ * is triggered, so this stays a thin link — `thumbs-embed` gains no
115
+ * questionnaire code and no build-time dependency on the quests package.
116
+ */
117
+ export interface QuestsLaunchConfig {
118
+ /**
119
+ * Base URL of the quest service. **Required to enable quest launching.**
120
+ * For QAid.dev this is e.g. `"https://qaid.dev/api/quests"`. The embed
121
+ * derives the quest definition URL (`{base}/{questId}/definition`) and the
122
+ * response endpoint (`{base}/responses`) from it.
123
+ */
124
+ base?: string;
125
+ /** Quest id launched after a thumbs-up (in place of the message box). */
126
+ up?: string;
127
+ /** Quest id launched after a thumbs-down. */
128
+ down?: string;
129
+ /** Quest id launched after a video recording is sent. */
130
+ video?: string;
131
+ /** API key for the quest service. Defaults to the top-level `apiKey`. */
132
+ apiKey?: string;
133
+ /**
134
+ * ES-module URL to lazy-load `@qaiddev/quests-embed` from at runtime.
135
+ * Default: the unpkg build pinned to a compatible major. Override to
136
+ * self-host or to point at a local build during development.
137
+ */
138
+ moduleUrl?: string;
102
139
  }
103
140
  /**
104
141
  * Network error captured during the session
@@ -249,4 +286,16 @@ export interface ResolvedFeedbackConfig {
249
286
  maxDuration: number;
250
287
  };
251
288
  recordIcon: string;
289
+ quests: {
290
+ /** Quest-service base URL. Empty string when quest launching is disabled. */
291
+ base: string;
292
+ /** Quest ids per button. Empty string means "no quest for this button". */
293
+ up: string;
294
+ down: string;
295
+ video: string;
296
+ /** Resolved quest API key (falls back to the top-level apiKey). */
297
+ apiKey: string;
298
+ /** Resolved ES-module URL for lazy-loading the quests widget. */
299
+ moduleUrl: string;
300
+ };
252
301
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * iOS/iPadOS screen-recording orientation fix.
3
+ *
4
+ * On iOS/iPadOS, `getDisplayMedia` + `MediaRecorder` writes a device-orientation
5
+ * rotation into the recorded file (even for screen captures). Players disagree
6
+ * on whether to honour it — the widget's own preview `<video>` renders it
7
+ * sideways, and so does the dashboard. Re-encoding the *live* decoded frames
8
+ * (which carry no container rotation) through a canvas produces a file whose
9
+ * pixels are already upright with no rotation metadata, so it renders the same
10
+ * everywhere.
11
+ *
12
+ * If the live frames themselves come back with swapped dimensions (portrait
13
+ * frame while the viewport is landscape, or vice versa), we additionally rotate
14
+ * the canvas 90° to match the viewport. That branch only triggers on a clear
15
+ * orientation mismatch, so a correctly-oriented recording is never rotated.
16
+ */
17
+ export interface OrientationTransform {
18
+ /** Output canvas width in px. */
19
+ width: number;
20
+ /** Output canvas height in px. */
21
+ height: number;
22
+ /** Clockwise rotation to apply while drawing: 0, 90, or -90. */
23
+ rotate: 0 | 90 | -90;
24
+ }
25
+ export interface OrientationCorrectedStream {
26
+ /** Canvas-sourced stream to hand to MediaRecorder. */
27
+ stream: MediaStream;
28
+ /** Stop drawing and release the canvas stream + source video. */
29
+ stop: () => void;
30
+ }
31
+ /** True on iPhone/iPad/iPod, including iPadOS 13+ which reports as desktop Mac. */
32
+ export declare function isIOSDevice(): boolean;
33
+ /**
34
+ * Decide the output canvas size and rotation needed to render a `frameW×frameH`
35
+ * capture upright for a `viewportW×viewportH` viewport. When the frame and
36
+ * viewport share an orientation, no rotation is applied (a straight re-encode,
37
+ * which still strips the bad container rotation). Otherwise rotate 90°, its
38
+ * direction chosen from the screen orientation angle.
39
+ */
40
+ export declare function computeOrientationTransform(frameW: number, frameH: number, viewportW: number, viewportH: number, screenAngle: number): OrientationTransform;
41
+ /** Draw one video frame onto the canvas context, applying the transform. */
42
+ export declare function drawRotatedFrame(ctx: CanvasRenderingContext2D, source: CanvasImageSource, t: OrientationTransform): void;
43
+ /**
44
+ * Build a canvas-backed, orientation-normalised copy of a display-capture
45
+ * stream. Returns null when the browser can't support the canvas pipeline, in
46
+ * which case the caller should record the original stream directly.
47
+ */
48
+ export declare function buildOrientationCorrectedStream(displayStream: MediaStream, frameRate?: number): Promise<OrientationCorrectedStream | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qaiddev/thumbs-embed",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Precision User Feedback",
5
5
  "type": "module",
6
6
  "main": "./dist/qaid.umd.cjs",