@tappify/extension-sdk 2.0.0 → 2.1.0-beta.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.
Files changed (148) hide show
  1. package/README.md +208 -0
  2. package/dist/chunk-2CY5HVY7.mjs +59 -0
  3. package/dist/chunk-2CY5HVY7.mjs.map +1 -0
  4. package/dist/{chunk-2DBSVKAU.cjs → chunk-6HETNZJY.cjs} +1 -1
  5. package/dist/chunk-6HETNZJY.cjs.map +1 -0
  6. package/dist/{chunk-PYJMJF44.cjs → chunk-7YKH4LZN.cjs} +9 -2
  7. package/dist/chunk-7YKH4LZN.cjs.map +1 -0
  8. package/dist/{chunk-RMUWRESK.cjs → chunk-A74HCO6T.cjs} +31 -18
  9. package/dist/chunk-A74HCO6T.cjs.map +1 -0
  10. package/dist/chunk-BPCGXXMD.cjs +781 -0
  11. package/dist/chunk-BPCGXXMD.cjs.map +1 -0
  12. package/dist/chunk-EB5XWQAU.cjs +132 -0
  13. package/dist/chunk-EB5XWQAU.cjs.map +1 -0
  14. package/dist/{chunk-5AAPD5WV.cjs → chunk-GAUZYBXE.cjs} +11 -11
  15. package/dist/chunk-GAUZYBXE.cjs.map +1 -0
  16. package/dist/chunk-J5LGTIGS.mjs +10 -0
  17. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  18. package/dist/{chunk-ZT35X7KL.mjs → chunk-KZ5NGFUP.mjs} +22 -9
  19. package/dist/chunk-KZ5NGFUP.mjs.map +1 -0
  20. package/dist/chunk-NN263PGQ.cjs +70 -0
  21. package/dist/chunk-NN263PGQ.cjs.map +1 -0
  22. package/dist/{chunk-3YWVYA42.cjs → chunk-O357OJXL.cjs} +14 -18
  23. package/dist/chunk-O357OJXL.cjs.map +1 -0
  24. package/dist/chunk-ONJMPO3P.cjs +59 -0
  25. package/dist/chunk-ONJMPO3P.cjs.map +1 -0
  26. package/dist/{chunk-OPPYEA7F.cjs → chunk-PRCZMOLD.cjs} +3 -3
  27. package/dist/chunk-PRCZMOLD.cjs.map +1 -0
  28. package/dist/chunk-Q7SFCCGT.cjs +10 -0
  29. package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
  30. package/dist/{chunk-QHM7XNRK.mjs → chunk-QA5AL3VZ.mjs} +2 -2
  31. package/dist/{chunk-QHM7XNRK.mjs.map → chunk-QA5AL3VZ.mjs.map} +1 -1
  32. package/dist/chunk-QJMZRV7A.mjs +132 -0
  33. package/dist/chunk-QJMZRV7A.mjs.map +1 -0
  34. package/dist/{chunk-CSTYBKZC.mjs → chunk-QXD7WVKK.mjs} +1 -1
  35. package/dist/chunk-QXD7WVKK.mjs.map +1 -0
  36. package/dist/{chunk-4C5Z3CIK.mjs → chunk-RBIQT75L.mjs} +2 -2
  37. package/dist/chunk-RBIQT75L.mjs.map +1 -0
  38. package/dist/{chunk-MNDVYPEP.mjs → chunk-SGCYEEXD.mjs} +9 -2
  39. package/dist/chunk-SGCYEEXD.mjs.map +1 -0
  40. package/dist/{chunk-BDCX6LRQ.mjs → chunk-SGEYDV3K.mjs} +3 -3
  41. package/dist/chunk-SGEYDV3K.mjs.map +1 -0
  42. package/dist/{chunk-IGK2AP6J.mjs → chunk-SLGOHIBL.mjs} +13 -17
  43. package/dist/chunk-SLGOHIBL.mjs.map +1 -0
  44. package/dist/chunk-SWG7CNTZ.mjs +781 -0
  45. package/dist/chunk-SWG7CNTZ.mjs.map +1 -0
  46. package/dist/{chunk-7KIKOH5H.cjs → chunk-VV7ELNRW.cjs} +3 -3
  47. package/dist/chunk-VV7ELNRW.cjs.map +1 -0
  48. package/dist/chunk-Z43DPNZ6.mjs +70 -0
  49. package/dist/chunk-Z43DPNZ6.mjs.map +1 -0
  50. package/dist/client-CV1qzQgx.d.cts +25 -0
  51. package/dist/client-CnlSHDWE.d.ts +25 -0
  52. package/dist/codegen.cjs.map +1 -1
  53. package/dist/codegen.d.cts +40 -7
  54. package/dist/codegen.d.ts +40 -7
  55. package/dist/codegen.mjs.map +1 -1
  56. package/dist/{constants-DJ_KgZpN.d.cts → constants-BAimnqOK.d.cts} +41 -8
  57. package/dist/{constants-DJ_KgZpN.d.ts → constants-BAimnqOK.d.ts} +41 -8
  58. package/dist/{context-Ml7k_V14.d.ts → context-Cr93B05o.d.ts} +5 -4
  59. package/dist/{context-Dt6xh_5t.d.cts → context-DJ3REtdT.d.cts} +5 -4
  60. package/dist/{errors-COsBh7kd.d.cts → errors-Cqh0wrqt.d.cts} +3 -2
  61. package/dist/{errors-COsBh7kd.d.ts → errors-Cqh0wrqt.d.ts} +3 -2
  62. package/dist/host.cjs +5 -4
  63. package/dist/host.cjs.map +1 -1
  64. package/dist/host.d.cts +5 -5
  65. package/dist/host.d.ts +5 -5
  66. package/dist/host.mjs +4 -3
  67. package/dist/host.mjs.map +1 -1
  68. package/dist/index.cjs +9 -656
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.cts +69 -627
  71. package/dist/index.d.ts +69 -627
  72. package/dist/index.mjs +5 -652
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/manifest.cjs +138 -85
  75. package/dist/manifest.cjs.map +1 -1
  76. package/dist/manifest.d.cts +93 -12
  77. package/dist/manifest.d.ts +93 -12
  78. package/dist/manifest.mjs +125 -72
  79. package/dist/manifest.mjs.map +1 -1
  80. package/dist/server.cjs +15 -66
  81. package/dist/server.cjs.map +1 -1
  82. package/dist/server.d.cts +6 -6
  83. package/dist/server.d.ts +6 -6
  84. package/dist/server.mjs +10 -61
  85. package/dist/server.mjs.map +1 -1
  86. package/dist/testing/index.cjs +73 -750
  87. package/dist/testing/index.cjs.map +1 -1
  88. package/dist/testing/index.d.cts +30 -11
  89. package/dist/testing/index.d.ts +30 -11
  90. package/dist/testing/index.mjs +89 -766
  91. package/dist/testing/index.mjs.map +1 -1
  92. package/dist/testing/jest.cjs +6 -5
  93. package/dist/testing/jest.cjs.map +1 -1
  94. package/dist/testing/jest.mjs +3 -2
  95. package/dist/testing/jest.mjs.map +1 -1
  96. package/dist/testing/vitest.cjs +6 -5
  97. package/dist/testing/vitest.cjs.map +1 -1
  98. package/dist/testing/vitest.d.cts +10 -0
  99. package/dist/testing/vitest.d.ts +10 -0
  100. package/dist/testing/vitest.mjs +3 -2
  101. package/dist/testing/vitest.mjs.map +1 -1
  102. package/dist/{types-DrpQSv_2.d.cts → types-BYGcp5pZ.d.ts} +4 -3
  103. package/dist/{types-DDAGLpov.d.ts → types-I1JOZyI_.d.cts} +4 -3
  104. package/dist/ui/styles.css +740 -0
  105. package/dist/ui.cjs +1018 -0
  106. package/dist/ui.cjs.map +1 -0
  107. package/dist/ui.d.cts +231 -0
  108. package/dist/ui.d.ts +231 -0
  109. package/dist/ui.mjs +1018 -0
  110. package/dist/ui.mjs.map +1 -0
  111. package/dist/vite-preview-bootstrap.cjs +66 -0
  112. package/dist/vite-preview-bootstrap.cjs.map +1 -0
  113. package/dist/vite-preview-bootstrap.d.cts +15 -0
  114. package/dist/vite-preview-bootstrap.d.ts +15 -0
  115. package/dist/vite-preview-bootstrap.mjs +66 -0
  116. package/dist/vite-preview-bootstrap.mjs.map +1 -0
  117. package/dist/vite-preview.cjs +876 -0
  118. package/dist/vite-preview.cjs.map +1 -0
  119. package/dist/vite-preview.d.cts +9 -0
  120. package/dist/vite-preview.d.ts +9 -0
  121. package/dist/vite-preview.mjs +876 -0
  122. package/dist/vite-preview.mjs.map +1 -0
  123. package/dist/vite.cjs +316 -59
  124. package/dist/vite.cjs.map +1 -1
  125. package/dist/vite.d.cts +51 -8
  126. package/dist/vite.d.ts +51 -8
  127. package/dist/vite.mjs +308 -53
  128. package/dist/vite.mjs.map +1 -1
  129. package/dist/work-events-DB3WR468.d.ts +547 -0
  130. package/dist/work-events-Dxe7Djdy.d.cts +547 -0
  131. package/package.json +19 -2
  132. package/schema/extension-v2.json +432 -121
  133. package/dist/chunk-2DBSVKAU.cjs.map +0 -1
  134. package/dist/chunk-3YWVYA42.cjs.map +0 -1
  135. package/dist/chunk-4C5Z3CIK.mjs.map +0 -1
  136. package/dist/chunk-5AAPD5WV.cjs.map +0 -1
  137. package/dist/chunk-7KIKOH5H.cjs.map +0 -1
  138. package/dist/chunk-BDCX6LRQ.mjs.map +0 -1
  139. package/dist/chunk-CSTYBKZC.mjs.map +0 -1
  140. package/dist/chunk-IGK2AP6J.mjs.map +0 -1
  141. package/dist/chunk-MNDVYPEP.mjs.map +0 -1
  142. package/dist/chunk-OPPYEA7F.cjs.map +0 -1
  143. package/dist/chunk-PYJMJF44.cjs.map +0 -1
  144. package/dist/chunk-RMUWRESK.cjs.map +0 -1
  145. package/dist/chunk-ZT35X7KL.mjs.map +0 -1
  146. package/dist/handler-CZIlZ5vu.d.ts +0 -227
  147. package/dist/handler-kU9Jf36K.d.cts +0 -227
  148. package/dist/styles.css +0 -367
@@ -0,0 +1,781 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+ var _chunkA74HCO6Tcjs = require('./chunk-A74HCO6T.cjs');
7
+
8
+
9
+
10
+ var _chunk7YKH4LZNcjs = require('./chunk-7YKH4LZN.cjs');
11
+
12
+
13
+
14
+ var _chunk6HETNZJYcjs = require('./chunk-6HETNZJY.cjs');
15
+
16
+ // src/testing/fixtures.ts
17
+ var fixtureApps = [
18
+ {
19
+ id: "app_ios",
20
+ name: "Northlight",
21
+ platform: "ios",
22
+ storeId: "1490000001",
23
+ bundleId: "ai.tappify.northlight"
24
+ },
25
+ {
26
+ id: "app_android",
27
+ name: "Northlight",
28
+ platform: "android",
29
+ storeId: "ai.tappify.northlight",
30
+ bundleId: "ai.tappify.northlight"
31
+ }
32
+ ];
33
+ var fixtureReleases = [
34
+ {
35
+ id: "rel_42",
36
+ version: "4.2.0",
37
+ build: "118",
38
+ platform: "ios",
39
+ state: "ready_for_sale",
40
+ shippedAt: "2026-09-01T09:00:00.000Z"
41
+ },
42
+ {
43
+ id: "rel_41",
44
+ version: "4.1.0",
45
+ build: "107",
46
+ platform: "ios",
47
+ state: "ready_for_sale",
48
+ shippedAt: "2026-08-11T09:00:00.000Z"
49
+ },
50
+ {
51
+ id: "rel_a42",
52
+ version: "4.2.0",
53
+ build: "4200",
54
+ platform: "android",
55
+ state: "published",
56
+ shippedAt: "2026-09-02T09:00:00.000Z"
57
+ }
58
+ ];
59
+ var fixtureKeywords = [
60
+ {
61
+ id: "kw_1",
62
+ term: "photo editor",
63
+ platform: "ios",
64
+ country: "US",
65
+ position: 4,
66
+ popularity: 6.6
67
+ },
68
+ {
69
+ id: "kw_2",
70
+ term: "collage maker",
71
+ platform: "ios",
72
+ country: "US",
73
+ position: 11,
74
+ popularity: 5.3
75
+ },
76
+ {
77
+ id: "kw_3",
78
+ term: "photo editor",
79
+ platform: "android",
80
+ country: "US",
81
+ position: 7,
82
+ popularity: 6
83
+ }
84
+ ];
85
+ var fixtureListings = [
86
+ {
87
+ platform: "ios",
88
+ country: "US",
89
+ locale: "en-US",
90
+ title: "Northlight \u2014 Photo Editor",
91
+ subtitle: "Edit, collage, share",
92
+ description: "Northlight turns a camera roll into a finished post.",
93
+ keywords: ["photo", "editor", "collage"],
94
+ updatedAt: "2026-08-28T12:00:00.000Z"
95
+ }
96
+ ];
97
+ var fixtureProject = {
98
+ project: {
99
+ id: "prj_north",
100
+ name: "Northlight",
101
+ platforms: ["ios", "android"]
102
+ },
103
+ apps: fixtureApps,
104
+ releases: fixtureReleases
105
+ };
106
+ var SERIES_VALUES = {
107
+ downloads: [1180, 1240, 1310, 1290, 1402, 1512, 1488],
108
+ impressions: [21400, 22150, 22980, 22110, 24020, 25510, 25010],
109
+ page_views: [8120, 8340, 8710, 8290, 9110, 9640, 9420],
110
+ conversion_rate: [0.145, 0.149, 0.15, 0.156, 0.152, 0.157, 0.158]
111
+ };
112
+ function week() {
113
+ return [
114
+ "2026-09-01",
115
+ "2026-09-02",
116
+ "2026-09-03",
117
+ "2026-09-04",
118
+ "2026-09-05",
119
+ "2026-09-06",
120
+ "2026-09-07"
121
+ ];
122
+ }
123
+ function fixtureSeries(metric) {
124
+ const points = week().map((at, index) => ({
125
+ at,
126
+ value: SERIES_VALUES[metric][index]
127
+ }));
128
+ return {
129
+ metric,
130
+ unit: metric === "conversion_rate" ? "ratio" : "count",
131
+ points
132
+ };
133
+ }
134
+ var fixtureReviews = {
135
+ total: 3,
136
+ averageRating: 3.7,
137
+ reviews: [
138
+ {
139
+ id: "rev_1",
140
+ platform: "ios",
141
+ country: "US",
142
+ rating: 5,
143
+ title: "Fast and clean",
144
+ body: "The new collage layouts are exactly what I wanted.",
145
+ author: "kbrady",
146
+ submittedAt: "2026-09-05T18:22:00.000Z",
147
+ replied: false
148
+ },
149
+ {
150
+ id: "rev_2",
151
+ platform: "ios",
152
+ country: "GB",
153
+ rating: 2,
154
+ title: "Crashes on export",
155
+ body: "Since 4.2 the export button closes the app.",
156
+ author: null,
157
+ submittedAt: "2026-09-04T08:10:00.000Z",
158
+ replied: true
159
+ },
160
+ {
161
+ id: "rev_3",
162
+ platform: "android",
163
+ country: "DE",
164
+ rating: 4,
165
+ title: null,
166
+ body: "Gute App, aber der Export dauert lange.",
167
+ author: "mgruber",
168
+ submittedAt: "2026-09-03T16:45:00.000Z",
169
+ replied: false
170
+ }
171
+ ]
172
+ };
173
+ var fixtureCrashes = {
174
+ crashFreeRate: 99.42,
175
+ issues: [
176
+ {
177
+ id: "crash_1",
178
+ title: "ExportSession.finish \u2014 index out of range",
179
+ count: 214,
180
+ build: "118",
181
+ firstSeenAt: "2026-09-01T11:02:00.000Z",
182
+ lastSeenAt: "2026-09-07T21:40:00.000Z"
183
+ },
184
+ {
185
+ id: "crash_2",
186
+ title: "PhotoLibrary.load \u2014 permission revoked",
187
+ count: 37,
188
+ build: "118",
189
+ firstSeenAt: "2026-09-02T07:15:00.000Z",
190
+ lastSeenAt: "2026-09-06T13:05:00.000Z"
191
+ }
192
+ ]
193
+ };
194
+ var fixtureRevenue = {
195
+ currency: "USD",
196
+ mrr: 41200,
197
+ refunds: 18,
198
+ points: week().map((at, index) => ({
199
+ at,
200
+ value: [1310, 1355, 1401, 1388, 1470, 1525, 1502][index]
201
+ }))
202
+ };
203
+ var fixtures = {
204
+ project: fixtureProject,
205
+ apps: fixtureApps,
206
+ releases: fixtureReleases,
207
+ keywords: fixtureKeywords,
208
+ listings: fixtureListings,
209
+ reviews: fixtureReviews,
210
+ crashes: fixtureCrashes,
211
+ revenue: fixtureRevenue,
212
+ series: fixtureSeries
213
+ };
214
+ function answerFixtureQuery(query) {
215
+ switch (query.kind) {
216
+ case "project":
217
+ return structuredClone(fixtureProject);
218
+ case "series":
219
+ return structuredClone(fixtureSeries(query.metric));
220
+ case "keywords":
221
+ return structuredClone({ keywords: fixtureKeywords });
222
+ case "listing":
223
+ return structuredClone({ listings: fixtureListings });
224
+ case "reviews":
225
+ return structuredClone(fixtureReviews);
226
+ case "crashes":
227
+ return structuredClone(fixtureCrashes);
228
+ case "revenue":
229
+ return structuredClone(fixtureRevenue);
230
+ }
231
+ }
232
+
233
+ // src/testing/client.ts
234
+
235
+
236
+
237
+
238
+ var _jose = require('jose');
239
+
240
+ // src/testing/envelope.ts
241
+ var DEFAULT_FILTERS = {
242
+ range: { from: "2026-09-01", to: "2026-09-08", preset: "7d" },
243
+ platform: "all",
244
+ country: "all"
245
+ };
246
+ function callEnvelope(envelope) {
247
+ const context = {
248
+ projectId: envelope.install.projectId,
249
+ platform: envelope.filters.platform,
250
+ from: envelope.filters.range.from,
251
+ to: envelope.filters.range.to,
252
+ filters: envelope.filters
253
+ };
254
+ return JSON.stringify({
255
+ install: envelope.install,
256
+ context,
257
+ input: envelope.input,
258
+ credentials: _nullishCoalesce(envelope.credentials, () => ( {})),
259
+ documents: _nullishCoalesce(envelope.documents, () => ( {})),
260
+ event: envelope.event
261
+ });
262
+ }
263
+ function parseJson(text) {
264
+ try {
265
+ return { parsed: true, value: JSON.parse(text) };
266
+ } catch (e) {
267
+ return { parsed: false, value: void 0 };
268
+ }
269
+ }
270
+ async function readEnvelopeResponse(response, call) {
271
+ const text = await response.text();
272
+ const body = parseJson(text);
273
+ const status = String(response.status);
274
+ if (response.ok) {
275
+ if (body.parsed) return body.value;
276
+ if (text.trim() === "") return void 0;
277
+ throw new (0, _chunk6HETNZJYcjs.TapServerError)(
278
+ _chunkA74HCO6Tcjs.SERVER_ERROR_CODES.INTERNAL_ERROR,
279
+ `The extension server answered ${status} for ${call} with a body that is not JSON. Every Tappify route answers JSON, or 204 with no body.`,
280
+ response.status
281
+ );
282
+ }
283
+ const error = _chunkA74HCO6Tcjs.readRecord.call(void 0, body.value, "error");
284
+ throw new (0, _chunk6HETNZJYcjs.TapServerError)(
285
+ _nullishCoalesce(_chunkA74HCO6Tcjs.readText.call(void 0, error, "code"), () => ( _chunkA74HCO6Tcjs.SERVER_ERROR_CODES.INTERNAL_ERROR)),
286
+ _nullishCoalesce(_chunkA74HCO6Tcjs.readText.call(void 0, error, "message"), () => ( `The extension server answered ${status} for ${call} without a JSON error body.`)),
287
+ response.status
288
+ );
289
+ }
290
+
291
+ // src/testing/client.ts
292
+ var TEST_KID = "tappify-test-key";
293
+ var TEST_ISSUER = "https://api.tappify.ai";
294
+ var keys = null;
295
+ function ensureKeys() {
296
+ _nullishCoalesce(keys, () => ( (keys = (async () => {
297
+ const pair = await _jose.generateKeyPair.call(void 0, "RS256", { extractable: true });
298
+ const publicJwk = await _jose.exportJWK.call(void 0, pair.publicKey);
299
+ return {
300
+ privateKey: pair.privateKey,
301
+ jwks: {
302
+ keys: [{ ...publicJwk, kid: TEST_KID, alg: "RS256", use: "sig" }]
303
+ }
304
+ };
305
+ })())));
306
+ return keys;
307
+ }
308
+ async function testJwks() {
309
+ return (await ensureKeys()).jwks;
310
+ }
311
+ async function signTestToken(claims = {}, options = {}) {
312
+ const { privateKey } = await ensureKeys();
313
+ const extensionId = _nullishCoalesce(_nullishCoalesce(options.extensionId, () => ( claims.extensionId)), () => ( "starter"));
314
+ return new (0, _jose.SignJWT)({
315
+ installId: _nullishCoalesce(claims.installId, () => ( "ins_test")),
316
+ extensionId,
317
+ projectId: _nullishCoalesce(claims.projectId, () => ( "prj_north")),
318
+ organizationId: _nullishCoalesce(claims.organizationId, () => ( "org_test")),
319
+ scopes: _nullishCoalesce(claims.scopes, () => ( [])),
320
+ ...claims.userId === void 0 ? {} : { userId: claims.userId }
321
+ }).setProtectedHeader({ alg: "RS256", kid: TEST_KID }).setIssuer(TEST_ISSUER).setAudience(`ext:${extensionId}`).setIssuedAt().setExpirationTime(_nullishCoalesce(options.expiresIn, () => ( "5m"))).sign(privateKey);
322
+ }
323
+ var HEALTH_PATH = "/tappify/health";
324
+ function createTestClient(target, defaults = {}) {
325
+ if (typeof target === "function") {
326
+ throw new (0, _chunk6HETNZJYcjs.TapError)(
327
+ "TAP_TEST_TARGET_INVALID",
328
+ "createTestClient() was handed a built handler instead of the options object. A handler from createTappifyHandler() verifies install tokens against the live Tappify key set, so every call the test client makes would come back 401. Pass the options themselves \u2014 createTestClient(handlerOptions) \u2014 or, when you need the handler, build it with createTappifyHandler({ ...handlerOptions, jwks: await testJwks() }) and drive it through createTapMock({ handler, extensionId: handlerOptions.extensionId }) so the test token carries the audience the handler expects."
329
+ );
330
+ }
331
+ const extensionId = target.extensionId;
332
+ const handlerPromise = testJwks().then(
333
+ (jwks) => _chunkA74HCO6Tcjs.createTappifyHandler.call(void 0, { ...target, jwks })
334
+ );
335
+ const install = {
336
+ id: "ins_test",
337
+ extensionId,
338
+ projectId: "prj_north",
339
+ organizationId: "org_test",
340
+ ...defaults.install
341
+ };
342
+ const filters = _nullishCoalesce(defaults.filters, () => ( DEFAULT_FILTERS));
343
+ const origin = _nullishCoalesce(defaults.origin, () => ( "https://vendor.test"));
344
+ let sent = 0;
345
+ function envelope(input, event) {
346
+ return callEnvelope({
347
+ install,
348
+ filters,
349
+ input,
350
+ credentials: defaults.credentials,
351
+ documents: defaults.documents,
352
+ event
353
+ });
354
+ }
355
+ async function send(method, path, body) {
356
+ const handler = await handlerPromise;
357
+ sent += 1;
358
+ const headers = new Headers({
359
+ "content-type": "application/json",
360
+ "x-tappify-event-id": _nullishCoalesce(defaults.eventId, () => ( `evt_${path}_${String(sent)}`))
361
+ });
362
+ if (path !== HEALTH_PATH) {
363
+ const token = await signTestToken(
364
+ {
365
+ installId: install.id,
366
+ extensionId: install.extensionId,
367
+ projectId: install.projectId,
368
+ organizationId: install.organizationId,
369
+ scopes: _nullishCoalesce(defaults.scopes, () => ( []))
370
+ },
371
+ { extensionId }
372
+ );
373
+ headers.set("authorization", `Bearer ${token}`);
374
+ }
375
+ return handler(
376
+ new Request(`${origin}${path}`, {
377
+ method,
378
+ headers,
379
+ ...body === void 0 ? {} : { body }
380
+ })
381
+ );
382
+ }
383
+ async function call(method, path, body) {
384
+ return readEnvelopeResponse(
385
+ await send(method, path, body),
386
+ `${method} ${path}`
387
+ );
388
+ }
389
+ return {
390
+ health: async () => await call("GET", HEALTH_PATH),
391
+ metrics: async (input) => await call(
392
+ "POST",
393
+ "/tappify/metrics",
394
+ envelope(input)
395
+ ),
396
+ tool: (id, input) => call("POST", `/tappify/tools/${id}`, envelope(input)),
397
+ mention: async (id, query) => await call(
398
+ "GET",
399
+ `/tappify/mentions/${id}?q=${encodeURIComponent(query)}`
400
+ ),
401
+ action: (id, input) => call("POST", `/tappify/actions/${id}`, envelope(input)),
402
+ procedure: (name, input = {}) => call("POST", `/tappify/procedures/${name}`, envelope(input)),
403
+ context: async (id) => await call("GET", `/tappify/context/${id}`),
404
+ event: async (name, payload) => {
405
+ await call("POST", "/tappify/events", envelope({}, { name, payload }));
406
+ },
407
+ work: (async (operation, input) => call(
408
+ "POST",
409
+ `/tappify/work/${operation}`,
410
+ envelope(input)
411
+ )),
412
+ raw: (method, path, body) => send(method, path, body === void 0 ? void 0 : JSON.stringify(body))
413
+ };
414
+ }
415
+
416
+ // src/testing/mock.ts
417
+ var SINGLETON_ID = "singleton";
418
+ var PROBE_KEYS = /* @__PURE__ */ new Set([
419
+ "then",
420
+ "toJSON",
421
+ "$$typeof",
422
+ "constructor",
423
+ "prototype",
424
+ "valueOf",
425
+ "toString",
426
+ "inspect",
427
+ "asymmetricMatch",
428
+ "nodeType"
429
+ ]);
430
+ function unknownCollectionError(collection) {
431
+ return new (0, _chunk6HETNZJYcjs.TapError)(
432
+ "TAP_UNKNOWN_COLLECTION",
433
+ `This mock exposes no storage collection called "${collection}". createTapMock({ storage: \u2026 }) was given an explicit map, so only the collections it names exist. Add "${collection}" to that map, or drop the storage option to expose every collection.`
434
+ );
435
+ }
436
+ function singletonView(handle) {
437
+ return {
438
+ get: () => handle.get(),
439
+ set: (document2) => handle.set(document2),
440
+ patch: (partial) => handle.patch(partial)
441
+ };
442
+ }
443
+ function collectionView(handle) {
444
+ return {
445
+ list: (listOptions) => handle.list(listOptions),
446
+ get: (id) => handle.get(id),
447
+ put: (id, document2) => handle.put(id, document2),
448
+ delete: (id) => handle.delete(id)
449
+ };
450
+ }
451
+ function scopeError(scope) {
452
+ return new (0, _chunk6HETNZJYcjs.TapError)(
453
+ "TAP_SCOPE_MISSING",
454
+ `This install has not granted ${scope}. Declare it under "scopes" in tappify.extension.json, or pass scopes: ["${scope}"] to renderWithTap() to test the granted path.`
455
+ );
456
+ }
457
+ function asObject(value) {
458
+ return typeof value === "object" && value !== null ? value : {};
459
+ }
460
+ function createTapMock(options = {}) {
461
+ const granted = new Set(_nullishCoalesce(options.scopes, () => ( _chunk7YKH4LZNcjs.SCOPE_KEYS)));
462
+ const listeners = /* @__PURE__ */ new Map();
463
+ const eventHandlers = /* @__PURE__ */ new Map();
464
+ const state = /* @__PURE__ */ new Map();
465
+ const store = /* @__PURE__ */ new Map();
466
+ const portal = document.createElement("div");
467
+ portal.dataset.tapPortal = "true";
468
+ const calls = {
469
+ navigations: [],
470
+ searches: [],
471
+ toasts: [],
472
+ confirms: [],
473
+ actions: [],
474
+ telemetry: [],
475
+ chats: [],
476
+ externals: [],
477
+ downloads: [],
478
+ copies: [],
479
+ cards: [],
480
+ expands: 0,
481
+ settingsOpens: 0
482
+ };
483
+ const emitChange = (key) => {
484
+ for (const listener of _nullishCoalesce(listeners.get(key), () => ( []))) listener();
485
+ };
486
+ const subscribe = (key, listener) => {
487
+ const set = _nullishCoalesce(listeners.get(key), () => ( /* @__PURE__ */ new Set()));
488
+ set.add(listener);
489
+ listeners.set(key, set);
490
+ return () => {
491
+ set.delete(listener);
492
+ };
493
+ };
494
+ const withStorage = (run) => granted.has("storage:write") ? Promise.resolve(run()) : Promise.reject(scopeError("storage:write"));
495
+ const bucket = (collection) => {
496
+ const existing = store.get(collection);
497
+ if (existing) return existing;
498
+ const created = [];
499
+ store.set(collection, created);
500
+ return created;
501
+ };
502
+ function buildHandle(collection) {
503
+ const find = (id) => bucket(collection).find((entry) => entry.id === id);
504
+ const write = (id, data) => {
505
+ const items = bucket(collection);
506
+ const index = items.findIndex((entry) => entry.id === id);
507
+ if (index === -1) items.push({ id, data });
508
+ else items[index] = { id, data };
509
+ emitChange(`storage:${collection}`);
510
+ };
511
+ return {
512
+ get: (id) => withStorage(() => _nullishCoalesce(_optionalChain([find, 'call', _ => _(_nullishCoalesce(id, () => ( SINGLETON_ID))), 'optionalAccess', _2 => _2.data]), () => ( null))),
513
+ set: (document2) => withStorage(() => {
514
+ write(SINGLETON_ID, document2);
515
+ }),
516
+ patch: (partial) => withStorage(() => {
517
+ const merged = { ...asObject(_optionalChain([find, 'call', _3 => _3(SINGLETON_ID), 'optionalAccess', _4 => _4.data])), ...partial };
518
+ write(SINGLETON_ID, merged);
519
+ return merged;
520
+ }),
521
+ list: () => withStorage(() => ({
522
+ items: bucket(collection).filter((entry) => entry.id !== SINGLETON_ID).map((entry) => ({ ...asObject(entry.data), id: entry.id }))
523
+ })),
524
+ put: (id, document2) => withStorage(() => {
525
+ write(id, document2);
526
+ }),
527
+ delete: (id) => withStorage(() => {
528
+ const items = bucket(collection);
529
+ const index = items.findIndex((entry) => entry.id === id);
530
+ if (index !== -1) {
531
+ items.splice(index, 1);
532
+ emitChange(`storage:${collection}`);
533
+ }
534
+ })
535
+ };
536
+ }
537
+ const handles = /* @__PURE__ */ new Map();
538
+ const kinds = options.storage;
539
+ const exposeHandle = (collection) => {
540
+ const kind = _optionalChain([kinds, 'optionalAccess', _5 => _5[collection]]);
541
+ if (kinds !== void 0 && kind === void 0) {
542
+ throw unknownCollectionError(collection);
543
+ }
544
+ const handle = buildHandle(collection);
545
+ if (kind === "singleton") return singletonView(handle);
546
+ if (kind === "collection") return collectionView(handle);
547
+ return handle;
548
+ };
549
+ const handleFor = (collection) => {
550
+ if (PROBE_KEYS.has(collection)) return void 0;
551
+ const existing = handles.get(collection);
552
+ if (existing) return existing;
553
+ const created = exposeHandle(collection);
554
+ handles.set(collection, created);
555
+ return created;
556
+ };
557
+ const storageTarget = {};
558
+ const storage = new Proxy(storageTarget, {
559
+ get: (_target, property) => typeof property === "string" ? handleFor(property) : void 0,
560
+ has: () => true,
561
+ ownKeys: () => [...store.keys()],
562
+ getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true })
563
+ });
564
+ let calledProcedures = 0;
565
+ let signedToken = null;
566
+ function handlerToken() {
567
+ if (options.handlerToken !== void 0) {
568
+ return Promise.resolve(options.handlerToken);
569
+ }
570
+ _nullishCoalesce(signedToken, () => ( (signedToken = signTestToken(
571
+ {
572
+ installId: tap.extension.installId,
573
+ extensionId: tap.extension.id,
574
+ projectId: tap.extension.projectId,
575
+ organizationId: tap.auth.organizationId,
576
+ scopes: [...granted]
577
+ },
578
+ { extensionId: tap.extension.id }
579
+ ))));
580
+ return signedToken;
581
+ }
582
+ async function callProcedure(name, input) {
583
+ const local = _optionalChain([options, 'access', _6 => _6.server, 'optionalAccess', _7 => _7[name]]);
584
+ if (local) return local(input);
585
+ if (options.handler) {
586
+ const path = `/tappify/procedures/${name}`;
587
+ calledProcedures += 1;
588
+ const response = await options.handler(
589
+ new Request(`https://vendor.test${path}`, {
590
+ method: "POST",
591
+ headers: {
592
+ "content-type": "application/json",
593
+ "x-tappify-event-id": `evt_${name}_${String(calledProcedures)}`,
594
+ authorization: `Bearer ${await handlerToken()}`
595
+ },
596
+ body: callEnvelope({
597
+ install: {
598
+ id: tap.extension.installId,
599
+ extensionId: tap.extension.id,
600
+ projectId: tap.extension.projectId,
601
+ organizationId: tap.auth.organizationId
602
+ },
603
+ filters: tap.filters,
604
+ input
605
+ })
606
+ })
607
+ );
608
+ return readEnvelopeResponse(response, `POST ${path}`);
609
+ }
610
+ throw new (0, _chunk6HETNZJYcjs.TapError)(
611
+ "TAP_UNKNOWN_PROCEDURE",
612
+ `No handler is registered for the procedure "${name}". Pass server: { ${name}: input => \u2026 } or handler: <your createTappifyHandler handler> to createTapMock().`
613
+ );
614
+ }
615
+ const serverTarget = {};
616
+ const tap = {
617
+ extension: {
618
+ id: _nullishCoalesce(options.extensionId, () => ( "starter")),
619
+ name: _nullishCoalesce(options.extensionName, () => ( "Starter")),
620
+ installId: _nullishCoalesce(options.installId, () => ( "ins_test")),
621
+ projectId: "prj_north"
622
+ },
623
+ env: "dev",
624
+ host: { version: "test", sdkMajors: [2] },
625
+ auth: {
626
+ token: "test-token",
627
+ user: { id: "usr_test", role: "admin" },
628
+ organizationId: "org_test",
629
+ scopes: [...granted],
630
+ can: (scope) => granted.has(scope)
631
+ },
632
+ project: {
633
+ id: "prj_north",
634
+ name: "Northlight",
635
+ platforms: ["ios", "android"],
636
+ apps: [],
637
+ releases: [],
638
+ keywords: [],
639
+ ...options.project
640
+ },
641
+ filters: { ...DEFAULT_FILTERS, ...options.filters },
642
+ theme: { mode: _nullishCoalesce(options.theme, () => ( "light")) },
643
+ locale: "en-US",
644
+ timezone: "UTC",
645
+ format: {
646
+ number: (value, formatOptions) => new Intl.NumberFormat("en-US", formatOptions).format(value),
647
+ currency: (value, code) => new Intl.NumberFormat("en-US", {
648
+ style: "currency",
649
+ currency: code
650
+ }).format(value),
651
+ date: (value) => new Date(value).toISOString().slice(0, 10),
652
+ relative: (value) => new Date(value).toISOString()
653
+ },
654
+ nav: {
655
+ push: (path) => calls.navigations.push(path),
656
+ setSearch: (params) => calls.searches.push(params),
657
+ openSettings: () => {
658
+ calls.settingsOpens += 1;
659
+ },
660
+ openChat: (prompt, context) => calls.chats.push({ prompt, context }),
661
+ openExternal: (url) => calls.externals.push(url)
662
+ },
663
+ data: {
664
+ query: (query) => {
665
+ const scope = _chunk7YKH4LZNcjs.QUERY_SCOPES[query.kind];
666
+ if (!granted.has(scope)) return Promise.reject(scopeError(scope));
667
+ return Promise.resolve(answerFixtureQuery(query));
668
+ },
669
+ subscribe: (event, handler) => {
670
+ const key = String(event);
671
+ const set = _nullishCoalesce(eventHandlers.get(key), () => ( /* @__PURE__ */ new Set()));
672
+ const wrapped = (payload) => {
673
+ handler(payload);
674
+ };
675
+ set.add(wrapped);
676
+ eventHandlers.set(key, set);
677
+ return () => {
678
+ set.delete(wrapped);
679
+ };
680
+ }
681
+ },
682
+ server: new Proxy(serverTarget, {
683
+ get: (_target, property) => (input) => callProcedure(String(property), input),
684
+ has: () => true
685
+ }),
686
+ state: {
687
+ get: (key) => {
688
+ const value = state.get(key);
689
+ return value === void 0 ? void 0 : value;
690
+ },
691
+ set: (key, value) => {
692
+ state.set(key, value);
693
+ emitChange(`state:${key}`);
694
+ },
695
+ subscribe: (key, handler) => subscribe(`state:${key}`, handler)
696
+ },
697
+ ui: {
698
+ size: _nullishCoalesce(options.size, () => ( "slot")),
699
+ toast: (message, tone = "neutral") => calls.toasts.push({ message, tone }),
700
+ confirm: (confirmOptions) => {
701
+ calls.confirms.push(confirmOptions);
702
+ return Promise.resolve(_nullishCoalesce(options.confirmAnswer, () => ( true)));
703
+ },
704
+ openInChat: (card) => calls.cards.push(card),
705
+ expand: () => {
706
+ calls.expands += 1;
707
+ },
708
+ download: (blob, filename) => calls.downloads.push({ filename, size: blob.size }),
709
+ copy: (text) => {
710
+ calls.copies.push(text);
711
+ return Promise.resolve();
712
+ }
713
+ },
714
+ context: _nullishCoalesce(options.context, () => ( {})),
715
+ params: _nullishCoalesce(options.params, () => ( {})),
716
+ storage,
717
+ telemetry: {
718
+ event: (name, props) => calls.telemetry.push({ name, props })
719
+ },
720
+ actions: {
721
+ run: (actionId, input) => {
722
+ calls.actions.push({ actionId, input });
723
+ return Promise.resolve({
724
+ id: `run_${String(calls.actions.length)}`,
725
+ actionId,
726
+ status: "pending",
727
+ requestedAt: (/* @__PURE__ */ new Date(0)).toISOString()
728
+ });
729
+ }
730
+ },
731
+ invalidate: () => void 0,
732
+ __portal: portal,
733
+ __subscribe: subscribe
734
+ };
735
+ return {
736
+ tap,
737
+ calls,
738
+ portal,
739
+ documents: (collection, documentId) => bucket(collection).filter((entry) => documentId === void 0 || entry.id === documentId).map((entry) => entry.data),
740
+ emit: (event, payload) => {
741
+ for (const handler of _nullishCoalesce(eventHandlers.get(String(event)), () => ( []))) {
742
+ handler(payload);
743
+ }
744
+ },
745
+ setFilters: (filters) => {
746
+ tap.filters = filters;
747
+ emitChange("filters");
748
+ },
749
+ setSize: (size) => {
750
+ tap.ui = { ...tap.ui, size };
751
+ emitChange("size");
752
+ },
753
+ setTheme: (mode) => {
754
+ tap.theme = { mode };
755
+ emitChange("theme");
756
+ },
757
+ setParams: (params) => {
758
+ tap.params = params;
759
+ emitChange("params");
760
+ }
761
+ };
762
+ }
763
+
764
+
765
+
766
+
767
+
768
+
769
+
770
+
771
+
772
+
773
+
774
+
775
+
776
+
777
+
778
+
779
+
780
+ exports.fixtureApps = fixtureApps; exports.fixtureReleases = fixtureReleases; exports.fixtureKeywords = fixtureKeywords; exports.fixtureListings = fixtureListings; exports.fixtureProject = fixtureProject; exports.fixtureSeries = fixtureSeries; exports.fixtureReviews = fixtureReviews; exports.fixtureCrashes = fixtureCrashes; exports.fixtureRevenue = fixtureRevenue; exports.fixtures = fixtures; exports.answerFixtureQuery = answerFixtureQuery; exports.testJwks = testJwks; exports.signTestToken = signTestToken; exports.createTestClient = createTestClient; exports.createTapMock = createTapMock;
781
+ //# sourceMappingURL=chunk-BPCGXXMD.cjs.map