@nadohq/mobile-client 0.66.0 → 0.67.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.
Files changed (156) hide show
  1. package/README.md +110 -17
  2. package/dist/{MobileClient-M4oC4lrX.cjs → MobileClient-vuI5wlB_.cjs} +69 -29
  3. package/dist/MobileClient-vuI5wlB_.cjs.map +1 -0
  4. package/dist/MobileClient.cjs +1 -1
  5. package/dist/MobileClient.d.cts +50 -25
  6. package/dist/MobileClient.d.cts.map +1 -1
  7. package/dist/MobileClient.d.ts +50 -25
  8. package/dist/MobileClient.d.ts.map +1 -1
  9. package/dist/MobileClient.js +69 -29
  10. package/dist/MobileClient.js.map +1 -1
  11. package/dist/{MobileServerFailureError-MtS2reel.d.cts → MobileServerFailureError-BeCCNShj.d.cts} +2 -2
  12. package/dist/{MobileServerFailureError-MtS2reel.d.cts.map → MobileServerFailureError-BeCCNShj.d.cts.map} +1 -1
  13. package/dist/{MobileServerFailureError-C9iqNsxI.d.ts → MobileServerFailureError-hHv28weI.d.ts} +2 -2
  14. package/dist/{MobileServerFailureError-C9iqNsxI.d.ts.map → MobileServerFailureError-hHv28weI.d.ts.map} +1 -1
  15. package/dist/{buildSignedMobileRequest-DyKCK34Y.d.cts → buildSignedMobileRequest-DcEMKU_4.d.cts} +2 -2
  16. package/dist/{buildSignedMobileRequest-DyKCK34Y.d.cts.map → buildSignedMobileRequest-DcEMKU_4.d.cts.map} +1 -1
  17. package/dist/{buildSignedMobileRequest-BR-KJhIF.d.ts → buildSignedMobileRequest-DlTMvoXR.d.ts} +2 -2
  18. package/dist/{buildSignedMobileRequest-BR-KJhIF.d.ts.map → buildSignedMobileRequest-DlTMvoXR.d.ts.map} +1 -1
  19. package/dist/{canonicalize-Bv6UIAbR.d.ts → canonicalize-BOZlflI3.d.ts} +2 -2
  20. package/dist/canonicalize-BOZlflI3.d.ts.map +1 -0
  21. package/dist/{canonicalize-CWh3XZzj.d.cts → canonicalize-BvCozzvJ.d.cts} +2 -2
  22. package/dist/canonicalize-BvCozzvJ.d.cts.map +1 -0
  23. package/dist/{clientTypes-DOpUcu80.d.cts → clientTypes-KpHlCDGZ.d.cts} +75 -49
  24. package/dist/clientTypes-KpHlCDGZ.d.cts.map +1 -0
  25. package/dist/{clientTypes-BUn5_OzK.d.ts → clientTypes-XRI41VdC.d.ts} +75 -49
  26. package/dist/clientTypes-XRI41VdC.d.ts.map +1 -0
  27. package/dist/dataMappers.cjs +56 -17
  28. package/dist/dataMappers.cjs.map +1 -1
  29. package/dist/dataMappers.d.cts +33 -13
  30. package/dist/dataMappers.d.cts.map +1 -1
  31. package/dist/dataMappers.d.ts +33 -13
  32. package/dist/dataMappers.d.ts.map +1 -1
  33. package/dist/dataMappers.js +53 -17
  34. package/dist/dataMappers.js.map +1 -1
  35. package/dist/{eip712MethodByType-CJ6BoayT.d.cts → eip712MethodByType-DyAmzH7b.d.cts} +2 -2
  36. package/dist/eip712MethodByType-DyAmzH7b.d.cts.map +1 -0
  37. package/dist/{eip712MethodByType-Dx4mvgni.d.ts → eip712MethodByType-qSSAF6i2.d.ts} +2 -2
  38. package/dist/eip712MethodByType-qSSAF6i2.d.ts.map +1 -0
  39. package/dist/feed-BrgkFqP3.d.cts +23 -0
  40. package/dist/feed-BrgkFqP3.d.cts.map +1 -0
  41. package/dist/feed-DcbhLoo1.d.ts +23 -0
  42. package/dist/feed-DcbhLoo1.d.ts.map +1 -0
  43. package/dist/index-BVVgDSdq.d.cts +1 -0
  44. package/dist/index-BVVgDSdq.d.ts +1 -0
  45. package/dist/index.cjs +2 -1
  46. package/dist/index.d.cts +13 -13
  47. package/dist/index.d.ts +13 -13
  48. package/dist/index.js +2 -2
  49. package/dist/{mobileErrorCodes-vD6rbFuF.d.ts → mobileErrorCodes-CdwX6QTO.d.cts} +3 -3
  50. package/dist/{mobileErrorCodes-vD6rbFuF.d.cts.map → mobileErrorCodes-CdwX6QTO.d.cts.map} +1 -1
  51. package/dist/{mobileErrorCodes-vD6rbFuF.d.cts → mobileErrorCodes-CdwX6QTO.d.ts} +3 -3
  52. package/dist/{mobileErrorCodes-vD6rbFuF.d.ts.map → mobileErrorCodes-CdwX6QTO.d.ts.map} +1 -1
  53. package/dist/{payloadHash-CdkF2Ocr.d.cts → payloadHash-BgGSZock.d.cts} +2 -2
  54. package/dist/payloadHash-BgGSZock.d.cts.map +1 -0
  55. package/dist/{payloadHash-HD29K77M.d.ts → payloadHash-Bi6upCLQ.d.ts} +2 -2
  56. package/dist/payloadHash-Bi6upCLQ.d.ts.map +1 -0
  57. package/dist/{serverExecuteTypes-5JyyIXRK.d.ts → serverExecuteTypes-DuCWe_H_.d.cts} +7 -14
  58. package/dist/{serverExecuteTypes-CHrQ9Rtq.d.cts.map → serverExecuteTypes-DuCWe_H_.d.cts.map} +1 -1
  59. package/dist/{serverExecuteTypes-CHrQ9Rtq.d.cts → serverExecuteTypes-usJ6HMJv.d.ts} +7 -14
  60. package/dist/{serverExecuteTypes-5JyyIXRK.d.ts.map → serverExecuteTypes-usJ6HMJv.d.ts.map} +1 -1
  61. package/dist/{serverModelTypes-CGUZCi0y.d.ts → serverModelTypes-CTHMNIQS.d.cts} +16 -14
  62. package/dist/serverModelTypes-CTHMNIQS.d.cts.map +1 -0
  63. package/dist/{serverModelTypes-CGUZCi0y.d.cts → serverModelTypes-CTHMNIQS.d.ts} +16 -14
  64. package/dist/serverModelTypes-CTHMNIQS.d.ts.map +1 -0
  65. package/dist/{serverResponseGuards-i1njcYKN.d.cts → serverResponseGuards-CzmldAad.d.cts} +2 -2
  66. package/dist/{serverResponseGuards-i1njcYKN.d.cts.map → serverResponseGuards-CzmldAad.d.cts.map} +1 -1
  67. package/dist/{serverResponseGuards-Bvw8ltYJ.d.ts → serverResponseGuards-Dvkbjp2_.d.ts} +2 -2
  68. package/dist/{serverResponseGuards-Bvw8ltYJ.d.ts.map → serverResponseGuards-Dvkbjp2_.d.ts.map} +1 -1
  69. package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
  70. package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
  71. package/dist/signing/canonicalize.cjs +4 -2
  72. package/dist/signing/canonicalize.cjs.map +1 -1
  73. package/dist/signing/canonicalize.d.cts +1 -1
  74. package/dist/signing/canonicalize.d.ts +1 -1
  75. package/dist/signing/canonicalize.js +4 -2
  76. package/dist/signing/canonicalize.js.map +1 -1
  77. package/dist/signing/eip712MethodByType.d.cts +1 -1
  78. package/dist/signing/eip712MethodByType.d.ts +1 -1
  79. package/dist/signing/index.d.cts +5 -5
  80. package/dist/signing/index.d.ts +5 -5
  81. package/dist/signing/payloadHash.d.cts +1 -1
  82. package/dist/signing/payloadHash.d.ts +1 -1
  83. package/dist/signing/types.d.cts +1 -1
  84. package/dist/signing/types.d.ts +1 -1
  85. package/dist/types/MobileServerFailureError.d.cts +1 -1
  86. package/dist/types/MobileServerFailureError.d.ts +1 -1
  87. package/dist/types/clientTypes.d.cts +2 -2
  88. package/dist/types/clientTypes.d.ts +2 -2
  89. package/dist/types/index.d.cts +7 -7
  90. package/dist/types/index.d.ts +7 -7
  91. package/dist/types/mobileErrorCodes.cjs +2 -2
  92. package/dist/types/mobileErrorCodes.cjs.map +1 -1
  93. package/dist/types/mobileErrorCodes.d.cts +1 -1
  94. package/dist/types/mobileErrorCodes.d.ts +1 -1
  95. package/dist/types/mobileErrorCodes.js +2 -2
  96. package/dist/types/mobileErrorCodes.js.map +1 -1
  97. package/dist/types/serverBaseTypes.d.cts +1 -1
  98. package/dist/types/serverBaseTypes.d.ts +1 -1
  99. package/dist/types/serverExecuteTypes.d.cts +2 -2
  100. package/dist/types/serverExecuteTypes.d.ts +2 -2
  101. package/dist/types/serverModelTypes.cjs.map +1 -1
  102. package/dist/types/serverModelTypes.d.cts +2 -2
  103. package/dist/types/serverModelTypes.d.ts +2 -2
  104. package/dist/types/serverModelTypes.js.map +1 -1
  105. package/dist/types/serverQueryTypes.d.cts +2 -2
  106. package/dist/types/serverQueryTypes.d.ts +2 -2
  107. package/dist/types/serverRequestTypes.d.cts +1 -1
  108. package/dist/types/serverRequestTypes.d.ts +1 -1
  109. package/dist/{types-C9YE4EIt.d.cts → types-CByl_NyJ.d.ts} +49 -18
  110. package/dist/types-CByl_NyJ.d.ts.map +1 -0
  111. package/dist/{types-Cwy1y_9x.d.ts → types-CR5iaKR4.d.cts} +49 -18
  112. package/dist/types-CR5iaKR4.d.cts.map +1 -0
  113. package/dist/utils/feed.cjs +9 -0
  114. package/dist/utils/feed.cjs.map +1 -1
  115. package/dist/utils/feed.d.cts +2 -2
  116. package/dist/utils/feed.d.ts +2 -2
  117. package/dist/utils/feed.js +9 -1
  118. package/dist/utils/feed.js.map +1 -1
  119. package/dist/utils/index.cjs +1 -0
  120. package/dist/utils/index.d.cts +3 -3
  121. package/dist/utils/index.d.ts +3 -3
  122. package/dist/utils/index.js +2 -2
  123. package/dist/utils/serverResponseGuards.d.cts +1 -1
  124. package/dist/utils/serverResponseGuards.d.ts +1 -1
  125. package/package.json +3 -3
  126. package/src/MobileClient.ts +82 -34
  127. package/src/dataMappers.test.ts +408 -9
  128. package/src/dataMappers.ts +76 -23
  129. package/src/signing/canonicalize.ts +6 -0
  130. package/src/signing/signing.test.ts +51 -0
  131. package/src/signing/types.ts +4 -0
  132. package/src/socialFeed.test.ts +53 -28
  133. package/src/types/clientTypes.ts +82 -47
  134. package/src/types/mobileErrorCodes.ts +2 -2
  135. package/src/types/serverExecuteTypes.ts +4 -12
  136. package/src/types/serverModelTypes.ts +19 -13
  137. package/src/types/serverQueryTypes.ts +32 -4
  138. package/src/types/serverRequestTypes.ts +20 -12
  139. package/src/utils/feed.ts +13 -0
  140. package/dist/MobileClient-M4oC4lrX.cjs.map +0 -1
  141. package/dist/canonicalize-Bv6UIAbR.d.ts.map +0 -1
  142. package/dist/canonicalize-CWh3XZzj.d.cts.map +0 -1
  143. package/dist/clientTypes-BUn5_OzK.d.ts.map +0 -1
  144. package/dist/clientTypes-DOpUcu80.d.cts.map +0 -1
  145. package/dist/eip712MethodByType-CJ6BoayT.d.cts.map +0 -1
  146. package/dist/eip712MethodByType-Dx4mvgni.d.ts.map +0 -1
  147. package/dist/feed-DErOLaoY.d.cts +0 -16
  148. package/dist/feed-DErOLaoY.d.cts.map +0 -1
  149. package/dist/feed-DErOLaoY.d.ts +0 -16
  150. package/dist/feed-DErOLaoY.d.ts.map +0 -1
  151. package/dist/payloadHash-CdkF2Ocr.d.cts.map +0 -1
  152. package/dist/payloadHash-HD29K77M.d.ts.map +0 -1
  153. package/dist/serverModelTypes-CGUZCi0y.d.cts.map +0 -1
  154. package/dist/serverModelTypes-CGUZCi0y.d.ts.map +0 -1
  155. package/dist/types-C9YE4EIt.d.cts.map +0 -1
  156. package/dist/types-Cwy1y_9x.d.ts.map +0 -1
@@ -82,6 +82,9 @@ export function canonicalizeMobileInner(
82
82
  ...(inner.image_ids === undefined
83
83
  ? {}
84
84
  : { image_ids: [...inner.image_ids] }),
85
+ ...(inner.image_urls === undefined
86
+ ? {}
87
+ : { image_urls: [...inner.image_urls] }),
85
88
  };
86
89
  case 'create_feed_comment':
87
90
  return {
@@ -99,6 +102,9 @@ export function canonicalizeMobileInner(
99
102
  ...(inner.image_ids === undefined
100
103
  ? {}
101
104
  : { image_ids: [...inner.image_ids] }),
105
+ ...(inner.image_urls === undefined
106
+ ? {}
107
+ : { image_urls: [...inner.image_urls] }),
102
108
  };
103
109
  case 'create_feed_post_image_uploads':
104
110
  case 'create_feed_comment_image_uploads':
@@ -653,3 +653,54 @@ describe('[mobile-client]: social content signing', () => {
653
653
  },
654
654
  );
655
655
  });
656
+
657
+ describe('[mobile-client]: external image signing', () => {
658
+ it.each(['create_feed_post', 'create_feed_comment'] as const)(
659
+ 'signs ordered original URL arrays after image_ids for %s',
660
+ (type) => {
661
+ const base = {
662
+ client_request_id: '11111111-1111-4111-8111-111111111111',
663
+ text: 'image test',
664
+ mentions: [],
665
+ };
666
+ const inner: MobileSignedInner =
667
+ type === 'create_feed_post'
668
+ ? {
669
+ type,
670
+ ...base,
671
+ position_id: '22222222-2222-4222-8222-222222222222',
672
+ }
673
+ : {
674
+ type,
675
+ ...base,
676
+ root_item_id: '22222222-2222-4222-8222-222222222222',
677
+ };
678
+ const urls = [
679
+ 'https://example.com/A.png?x=%2f',
680
+ 'https://example.com/b.png',
681
+ ];
682
+ const result = canonicalizeMobileInner({
683
+ image_urls: urls,
684
+ image_ids: [],
685
+ ...inner,
686
+ });
687
+ expect(Object.keys(result).slice(-2)).toEqual([
688
+ 'image_ids',
689
+ 'image_urls',
690
+ ]);
691
+ if (result.type !== type) throw new Error('Unexpected signing type');
692
+ expect(result.image_urls).toEqual(urls);
693
+ expect(result.image_urls).not.toBe(urls);
694
+ const hash = (imageUrls?: string[]) =>
695
+ getMobilePayloadHash(
696
+ canonicalizeMobileInner({ ...inner, image_urls: imageUrls }),
697
+ );
698
+ expect(hash()).not.toBe(hash([]));
699
+ expect(hash(urls)).not.toBe(hash([...urls].reverse()));
700
+ expect(hash(urls)).not.toBe(
701
+ hash([urls[0].replace('%2f', '%2F'), urls[1]]),
702
+ );
703
+ expect(hash(urls)).toBe(hash([...urls]));
704
+ },
705
+ );
706
+ });
@@ -70,6 +70,8 @@ export type MobileSignedInner =
70
70
  drawing_data_json?: string;
71
71
  /** Ordered ready image UUIDs; omitted when absent. */
72
72
  image_ids?: string[];
73
+ /** Up to ten original HTTP/HTTPS URLs in order; omission differs from an empty array. */
74
+ image_urls?: string[];
73
75
  }
74
76
  | {
75
77
  /** Atomic comment publication. */
@@ -88,6 +90,8 @@ export type MobileSignedInner =
88
90
  attached_position_id?: string;
89
91
  /** Ordered ready image UUIDs; omitted when absent. */
90
92
  image_ids?: string[];
93
+ /** Up to ten original HTTP/HTTPS URLs in order; omission differs from an empty array. */
94
+ image_urls?: string[];
91
95
  }
92
96
  | {
93
97
  /** Direct multipart post-image upload; content_hash is Keccak256 of the raw file bytes. */
@@ -82,7 +82,7 @@ const readSession: MobileServerSocialFeedReadSession = {
82
82
  session_id: 'OpaqueSession',
83
83
  mode: 'feed',
84
84
  expires_at_ms: 42,
85
- view_tracking: { mode: 'detail_visit' },
85
+
86
86
  update_cursor: null,
87
87
  viewer_token: 'OpaqueViewer',
88
88
  };
@@ -180,7 +180,6 @@ describe('[mobile-client]: social feed transport', () => {
180
180
  });
181
181
  await client.getFeedItem({
182
182
  itemId: itemId.toUpperCase(),
183
- viewEventId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
184
183
  commentPreviewLimit: 0,
185
184
  viewAs,
186
185
  });
@@ -223,9 +222,7 @@ describe('[mobile-client]: social feed transport', () => {
223
222
  expect(requests[3].body.subaccount).not.toBe(requests[3].body.view_as);
224
223
  expect(requests[5].body.position_id).toBe(positionId.toUpperCase());
225
224
  expect(requests[6].body.item_id).toBe(itemId.toUpperCase());
226
- expect(requests[6].body.view_event_id).toBe(
227
- 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
228
- );
225
+ expect(requests[6].body).not.toHaveProperty('view_event_id');
229
226
  expect(requests[6].body.comment_preview_limit).toBe(0);
230
227
  expect(requests[7].body).toEqual({
231
228
  type: 'feed_updates',
@@ -239,35 +236,63 @@ describe('[mobile-client]: social feed transport', () => {
239
236
  },
240
237
  );
241
238
 
242
- it('reports detail visits with the registered view event over public_execute', async () => {
243
- const { client, requests } = mockClient({
244
- status: 'success',
245
- accepted_item_ids: [itemId],
246
- duplicate_item_ids: [],
247
- });
239
+ it('reports multiple IDs and same-request duplicates without session fields or a signature', async () => {
240
+ const { client, requests } = mockClient({ status: 'success' });
248
241
  client.opts.walletClient = undefined;
249
- const result = await client.recordFeedViews({
250
- sessionId: 'OpaqueSession',
251
- viewerToken: 'OpaqueViewer',
252
- viewEventId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
253
- itemId,
254
- });
255
- expect(result).toEqual({
256
- acceptedItemIds: [itemId],
257
- duplicateItemIds: [],
242
+ const itemIds = [itemId, 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', itemId];
243
+ expect(await client.recordFeedViews({ itemIds })).toEqual({
244
+ status: 'success',
258
245
  });
259
246
  expect(requests).toEqual([
260
247
  {
261
248
  url: 'https://mobile.example/v1/mobile/public_execute',
262
- body: {
263
- type: 'record_feed_views',
264
- session_id: 'OpaqueSession',
265
- viewer_token: 'OpaqueViewer',
266
- view_event_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
267
- item_ids: [itemId],
268
- },
249
+ body: { type: 'record_feed_views', item_ids: itemIds },
269
250
  },
270
251
  ]);
252
+ // A caller-initiated repeat remains a new report, not a cached or locally deduplicated result.
253
+ await client.recordFeedViews({ itemIds });
254
+ expect(requests).toHaveLength(2);
255
+ expect(requests[1].body).toEqual(requests[0].body);
256
+ });
257
+
258
+ it.each([
259
+ [400, 6310],
260
+ [503, 1006],
261
+ [503, 6320],
262
+ ])(
263
+ 'preserves batch view failures (%i/%i) without retrying',
264
+ async (httpStatus, errorCode) => {
265
+ const { client, requests } = mockClient(
266
+ {
267
+ status: 'failure',
268
+ error: 'Batch rejected',
269
+ error_code: errorCode,
270
+ request_type: 'public_execute_record_feed_views',
271
+ },
272
+ httpStatus,
273
+ );
274
+ await expect(
275
+ client.recordFeedViews({ itemIds: [itemId] }),
276
+ ).rejects.toMatchObject({
277
+ name: 'MobileServerFailureError',
278
+ errorCode,
279
+ httpStatus,
280
+ });
281
+ expect(requests).toHaveLength(1);
282
+ },
283
+ );
284
+
285
+ it('does not retry a view report when the response may have been lost', async () => {
286
+ const { client } = mockClient();
287
+ let attempts = 0;
288
+ client.axiosInstance.defaults.adapter = () => {
289
+ attempts++;
290
+ return Promise.reject(new Error('Connection closed after sending'));
291
+ };
292
+ await expect(client.recordFeedViews({ itemIds: [itemId] })).rejects.toThrow(
293
+ 'Connection closed',
294
+ );
295
+ expect(attempts).toBe(1);
271
296
  });
272
297
 
273
298
  it('preserves update IDs, session credentials, and cursors unchanged', async () => {
@@ -344,7 +369,7 @@ describe('[mobile-client]: social feed transport', () => {
344
369
  session_id: 'Session',
345
370
  mode: 'feed',
346
371
  expires_at_ms: 42,
347
- view_tracking: { mode: 'detail_visit' },
372
+
348
373
  update_cursor: null,
349
374
  viewer_token: 'Viewer',
350
375
  },
@@ -938,21 +938,12 @@ export interface MobileSocialFeedCommentPreview {
938
938
  items: MobileSocialFeedReply[];
939
939
  /** Comment count. */
940
940
  totalCount: number;
941
- /** Comment cursor, if available. */
941
+ /** Opens the first complete group page in the same snapshot; do not prepend preview items. */
942
942
  nextCursor: string | null;
943
943
  }
944
944
 
945
- /** Exposure requirements associated with a reading session. */
946
- export interface MobileSocialFeedViewTracking {
947
- /**
948
- * Detail-visit exposure: report the primary detail target after it is shown in the foreground.
949
- * List visible-ratio / dwell thresholds are no longer used.
950
- */
951
- mode: 'detail_visit';
952
- }
953
-
954
945
  /** Uploaded feed image metadata; bytes are fetched separately with the reading session. */
955
- export interface MobileSocialFeedImage {
946
+ export interface MobileSocialFeedUploadedImage {
956
947
  /** Image UUID. */
957
948
  imageId: string;
958
949
  /** Backend path for the original image bytes. */
@@ -969,6 +960,17 @@ export interface MobileSocialFeedImage {
969
960
  thumbnail: MobileSocialFeedImageThumbnail;
970
961
  }
971
962
 
963
+ /** External image loaded directly from its URL without Mobile session headers. */
964
+ export interface MobileSocialFeedExternalImage {
965
+ /** Original HTTP/HTTPS URL; no upload metadata or thumbnail is provided. */
966
+ url: string;
967
+ }
968
+
969
+ /** Uploaded image metadata or an external URL; uploaded images precede external images. */
970
+ export type MobileSocialFeedImage =
971
+ | MobileSocialFeedUploadedImage
972
+ | MobileSocialFeedExternalImage;
973
+
972
974
  /** Thumbnail metadata for an uploaded feed image. */
973
975
  export interface MobileSocialFeedImageThumbnail {
974
976
  /** Backend path for the thumbnail bytes. */
@@ -1109,8 +1111,6 @@ export interface MobileSocialFeedReadSession {
1109
1111
  mode: MobileSocialFeedReadSessionMode;
1110
1112
  /** Current idle expiration in Unix milliseconds; legitimate session requests renew it. */
1111
1113
  expiresAtMs: number;
1112
- /** Visibility requirements for explicit exposure reporting. */
1113
- viewTracking: MobileSocialFeedViewTracking;
1114
1114
  /** Initial update baseline, distinct from the page cursor. */
1115
1115
  updateCursor: string | null;
1116
1116
  /** Credential for this reading session. Do not log or cache publicly. */
@@ -1275,12 +1275,6 @@ export type GetMobilePositionFeedParams = MobileSocialFeedListParams &
1275
1275
  export type GetMobileFeedItemParams = MobileSocialFeedReadParams & {
1276
1276
  /** Target item UUID. */
1277
1277
  itemId: string;
1278
- /**
1279
- * Detail-visit event UUID generated once per real navigation into this item.
1280
- * Reuse the same value on retries for the same visit; omit for prefetch.
1281
- * Registering the event does not increment the view count by itself.
1282
- */
1283
- viewEventId?: string;
1284
1278
  /** Preview size, 0–3. */
1285
1279
  commentPreviewLimit?: number;
1286
1280
  /** Existing session authorized for the item. */
@@ -1598,6 +1592,12 @@ export interface MobileCreateFeedPostParams extends MobileSignedRequestParams {
1598
1592
  * Images alone are not enough — nonblank text or a valid drawing is still required.
1599
1593
  */
1600
1594
  imageIds?: string[];
1595
+ /**
1596
+ * Up to ten original HTTP/HTTPS URLs in display order, independent of the nine upload slots.
1597
+ * No whitespace or control characters. Omit or pass []; null is invalid. Preserve URL spelling,
1598
+ * order, and field presence for retries: omission and [] produce different signing hashes.
1599
+ */
1600
+ imageUrls?: string[];
1601
1601
  }
1602
1602
 
1603
1603
  /**
@@ -1611,7 +1611,7 @@ export interface MobileCreateFeedImageUploadParams extends MobileSignedRequestPa
1611
1611
 
1612
1612
  /** Parameters for preparing a protected feed image request with a reading session. */
1613
1613
  export interface GetMobileFeedImageRequestParams {
1614
- /** Backend path from {@link MobileSocialFeedImage.url} or thumbnail.url. */
1614
+ /** Uploaded image path from {@link MobileSocialFeedUploadedImage.url} or thumbnail.url; load external URLs directly without session headers. */
1615
1615
  path: string;
1616
1616
  /** Reading session that fully delivered the parent item. */
1617
1617
  sessionId: string;
@@ -1652,9 +1652,9 @@ export interface GetMobileFeedCommentsParams extends MobileSocialFeedReadParams
1652
1652
  rootItemId: string;
1653
1653
  /** Forward or backward opaque cursor from the same session. */
1654
1654
  cursor?: string;
1655
- /** Page size, 1-50; defaults to 20. */
1655
+ /** Top-level group count, 1-50; defaults to 20. Descendants do not consume the limit. */
1656
1656
  limit?: number;
1657
- /** First-page reply anchor; cannot be combined with cursor. */
1657
+ /** First-page anchor at any depth; returns its top-level group. Cannot be combined with cursor. */
1658
1658
  anchorReplyId?: string;
1659
1659
  /** Reuse the original session and viewerToken when navigating or paging. */
1660
1660
  sessionId?: string;
@@ -1679,6 +1679,12 @@ export interface MobileCreateFeedCommentParams extends MobileSignedRequestParams
1679
1679
  * Omit or pass `[]` when none; images cannot replace the required nonblank text.
1680
1680
  */
1681
1681
  imageIds?: string[];
1682
+ /**
1683
+ * Up to ten original HTTP/HTTPS URLs in display order, independent of the nine upload slots.
1684
+ * No whitespace or control characters. Omit or pass []; null is invalid. Preserve URL spelling,
1685
+ * order, and field presence for retries: omission and [] produce different signing hashes.
1686
+ */
1687
+ imageUrls?: string[];
1682
1688
  }
1683
1689
 
1684
1690
  /** Parameters for setting or cancelling a signed user's like. */
@@ -1689,22 +1695,13 @@ export interface MobileSetFeedLikeParams extends MobileSignedRequestParams {
1689
1695
  isLiked: boolean;
1690
1696
  }
1691
1697
 
1692
- /**
1693
- * Unsigned detail-visit report. Call after the primary detail target is shown in the foreground,
1694
- * using the same {@link GetMobileFeedItemParams.viewEventId} previously sent to `getFeedItem`.
1695
- */
1698
+ /** Unsigned batch view report, independent of reading sessions and prior detail reads. */
1696
1699
  export interface MobileRecordFeedViewsParams {
1697
- /** Original reading session returned by the detail read. */
1698
- sessionId: string;
1699
- /** Original opaque browsing credential from that read session. */
1700
- viewerToken: string;
1701
1700
  /**
1702
- * Detail-visit event UUID registered on the preceding feed_item call.
1703
- * Reuse across retries for the same visit; generate a new UUID only after leaving and re-entering.
1701
+ * 1–50 canonical lowercase item UUIDs before deduplication. Posts, trade cards, comments, and replies
1702
+ * may be mixed. Same-request duplicates count once; repeating the request counts again.
1704
1703
  */
1705
- viewEventId: string;
1706
- /** Exactly one fully delivered, readable detail-target item UUID. */
1707
- itemId: string;
1704
+ itemIds: string[];
1708
1705
  }
1709
1706
 
1710
1707
  /** Parameters for full position history, not just published feed items. */
@@ -1721,6 +1718,24 @@ export interface GetMobilePositionHistoryParams extends MobileSocialFeedReadPara
1721
1718
  sessionId?: string;
1722
1719
  }
1723
1720
 
1721
+ /** Parameters for the most recent 500 actions, without pagination or authored content. */
1722
+ export interface GetMobilePositionHistoryAllParams extends MobileSocialFeedReadParams {
1723
+ /** Lifecycle UUID. */
1724
+ positionId: string;
1725
+ }
1726
+
1727
+ /** Parameters for published position thoughts containing both nonblank text and a saved drawing. */
1728
+ export interface GetMobilePositionThoughtsParams extends MobileSocialFeedReadParams {
1729
+ /** Lifecycle UUID; reopening a position creates a different lifecycle. */
1730
+ positionId: string;
1731
+ /** Page size, 1-50; defaults to 20 and must remain unchanged across pages. */
1732
+ limit?: number;
1733
+ /** Original thoughts session; cannot be reused from history or feed queries. */
1734
+ sessionId?: string;
1735
+ /** Previous nextCursor; requires the original session and viewerToken. */
1736
+ cursor?: string;
1737
+ }
1738
+
1724
1739
  /** Counts across the authorized position history. */
1725
1740
  export interface MobileSocialFeedHistorySummary {
1726
1741
  /** Action count. */
@@ -1839,14 +1854,22 @@ export interface MobileSocialFeedDrawingMutationResult {
1839
1854
  drawing: MobileSocialFeedDrawing;
1840
1855
  }
1841
1856
 
1842
- /** Comment thread page, including both directions around an anchor. */
1857
+ /** Top-level comment and all readable descendants from the thread snapshot, without recursive groups. */
1858
+ export interface MobileSocialFeedCommentGroup extends MobileSocialFeedReply {
1859
+ /** Flat descendants in server order: creation time ascending, then depth and UUID. Empty when none. */
1860
+ replies: MobileSocialFeedReply[];
1861
+ }
1862
+
1863
+ /** Comment-group page, including both directions around an anchor at any depth. */
1843
1864
  export interface MobileSocialFeedCommentsPage {
1844
1865
  /** Requested anchor, or null when reading without one. */
1845
1866
  anchorReplyId: string | null;
1846
- /** Readable replies in service order. */
1847
- comments: MobileSocialFeedReply[];
1848
- /** Total readable comment count. */
1867
+ /** Top-level comments, newest first, each with its complete readable descendants; never split a group. */
1868
+ comments: MobileSocialFeedCommentGroup[];
1869
+ /** All readable replies in the thread snapshot, including descendants; not just this page. */
1849
1870
  totalCount: number;
1871
+ /** All readable top-level comments in the thread snapshot; not just this page. */
1872
+ topLevelCount: number;
1850
1873
  /** Forward cursor, or null. */
1851
1874
  nextCursor: string | null;
1852
1875
  /** Backward cursor, or null. */
@@ -1875,16 +1898,10 @@ export interface MobileSocialFeedLikeResult {
1875
1898
  version: string;
1876
1899
  }
1877
1900
 
1878
- /** Exposure deduplication result for the current UTC day. */
1879
- export interface MobileSocialFeedViewsResult {
1880
- /** Newly counted item UUIDs. */
1881
- acceptedItemIds: string[];
1882
- /** Already counted item UUIDs. */
1883
- duplicateItemIds: string[];
1884
- }
1885
-
1886
1901
  /** Full position history and viewing-account trading hints. */
1887
1902
  export interface MobileSocialFeedPositionHistory {
1903
+ /** Current position owner; never inferred from the viewer. */
1904
+ owner: MobileSocialFeedAuthor;
1888
1905
  /** Position and additional history metrics. */
1889
1906
  position: MobileSocialFeedPositionHistoryCard;
1890
1907
  /** Whole-history counts. */
@@ -1947,3 +1964,21 @@ export interface MobileSocialFeedMentionParams extends Omit<
1947
1964
  /** Mentioned identity; serialized to bytes32 for signing. */
1948
1965
  subaccount: Subaccount;
1949
1966
  }
1967
+
1968
+ /** Published position thought with nonblank text and a complete saved drawing. */
1969
+ export type MobileSocialFeedPositionThought = MobileSocialFeedHistoryTake & {
1970
+ /** Always true for this endpoint; uploaded or external images alone do not qualify. */
1971
+ hasAnalysis: true;
1972
+ /** Parsed current drawing; saved data does not necessarily contain visible strokes. */
1973
+ drawing: MobileSocialFeedDrawing;
1974
+ };
1975
+
1976
+ /** Thoughts in descending drawing creation time and item UUID order; no 500-item total cap. */
1977
+ export interface MobileSocialFeedPositionThoughtsPage {
1978
+ /** Complete content in server order; never reorder by display time. */
1979
+ items: MobileSocialFeedPositionThought[];
1980
+ /** Null only at the directory end; an empty page can still have a cursor. */
1981
+ nextCursor: string | null;
1982
+ /** Independent position session; newly published content requires a refresh. */
1983
+ readSession: MobileSocialFeedReadSession;
1984
+ }
@@ -96,8 +96,8 @@ export const MOBILE_ERROR_CODES = {
96
96
  SOCIAL_FEED_QUERY_REMOVED: 6325,
97
97
  /** Image format, dimensions, integrity, ownership, readiness, expiry, hash, or count is invalid. */
98
98
  INVALID_SOCIAL_FEED_IMAGE: 6326,
99
- /** An existing detail-visit event was reused with a different item or browsing credential. */
100
- SOCIAL_FEED_VIEW_EVENT_REBOUND: 6327,
99
+ /** Initial history/thoughts directory exceeds 4 MiB; do not automatically retry the same query. */
100
+ SOCIAL_FEED_HISTORY_TOO_LARGE: 6328,
101
101
  } as const;
102
102
 
103
103
  /**
@@ -6,9 +6,9 @@ import {
6
6
  import {
7
7
  MobileServerSocialFeedAuthor,
8
8
  MobileServerSocialFeedDrawing,
9
- MobileServerSocialFeedImage,
10
9
  MobileServerSocialFeedItem,
11
10
  MobileServerSocialFeedReply,
11
+ MobileServerSocialFeedUploadedImage,
12
12
  } from './serverModelTypes';
13
13
  import { MobileServerNotificationSettingsV2Response } from './serverQueryTypes';
14
14
  import {
@@ -85,7 +85,7 @@ export interface MobileServerAvatarUploadResponse {
85
85
  /** Ready image returned by a direct multipart upload. */
86
86
  export interface MobileServerSocialFeedImageUploadResponse {
87
87
  /** Ready image metadata, including original and thumbnail paths. */
88
- image: MobileServerSocialFeedImage;
88
+ image: MobileServerSocialFeedUploadedImage;
89
89
  }
90
90
 
91
91
  /**
@@ -177,14 +177,6 @@ export interface MobileServerSocialFeedLikeResponse {
177
177
  version: string;
178
178
  }
179
179
 
180
- /** Exposure counts deduplicated by browsing credential, item, and UTC day. */
181
- export interface MobileServerSocialFeedViewsResponse {
182
- /** Newly counted items. */
183
- accepted_item_ids: string[];
184
- /** Already counted items. */
185
- duplicate_item_ids: string[];
186
- }
187
-
188
180
  /** Page lease result; this response does not carry as_of_ms. */
189
181
  export interface MobileServerSocialFeedPresenceResponse {
190
182
  /** Active visitor count. */
@@ -205,8 +197,8 @@ export interface MobileServerPublicExecuteResponseByType {
205
197
  unregister_expo_token: MobileServerEmptyExecuteResponse;
206
198
  /** Preference updates return only the success envelope. */
207
199
  update_preferences: MobileServerEmptyExecuteResponse;
208
- /** Exposure deduplication result. */
209
- record_feed_views: MobileServerSocialFeedViewsResponse;
200
+ /** Batch views return only status; no per-target result or count. */
201
+ record_feed_views: MobileServerEmptyExecuteResponse;
210
202
  /** Page lease result. */
211
203
  position_presence: MobileServerSocialFeedPresenceResponse;
212
204
  }
@@ -592,21 +592,12 @@ export interface MobileServerSocialFeedCommentPreview {
592
592
  items: MobileServerSocialFeedReply[];
593
593
  /** Comment count. */
594
594
  total_count: number;
595
- /** Comment cursor, if available. */
595
+ /** Opens the first complete group page in the same snapshot; do not prepend preview items. */
596
596
  next_cursor: string | null;
597
597
  }
598
598
 
599
- /** Exposure requirements associated with a reading session. */
600
- export interface MobileServerSocialFeedViewTracking {
601
- /**
602
- * Detail-visit exposure: report the primary detail target after it is shown in the foreground.
603
- * List visible-ratio / dwell thresholds are no longer used.
604
- */
605
- mode: 'detail_visit';
606
- }
607
-
608
599
  /** Uploaded feed image metadata; bytes are fetched separately with the reading session. */
609
- export interface MobileServerSocialFeedImage {
600
+ export interface MobileServerSocialFeedUploadedImage {
610
601
  /** Image UUID. */
611
602
  image_id: string;
612
603
  /** Backend path for the original image bytes. */
@@ -623,6 +614,17 @@ export interface MobileServerSocialFeedImage {
623
614
  thumbnail: MobileServerSocialFeedImageThumbnail;
624
615
  }
625
616
 
617
+ /** External image loaded directly from its URL without Mobile session headers. */
618
+ export interface MobileServerSocialFeedExternalImage {
619
+ /** Original HTTP/HTTPS URL; no upload metadata or thumbnail is provided. */
620
+ url: string;
621
+ }
622
+
623
+ /** Uploaded image metadata or an external URL; uploaded images precede external images. */
624
+ export type MobileServerSocialFeedImage =
625
+ | MobileServerSocialFeedUploadedImage
626
+ | MobileServerSocialFeedExternalImage;
627
+
626
628
  /** Thumbnail metadata for an uploaded feed image. */
627
629
  export interface MobileServerSocialFeedImageThumbnail {
628
630
  /** Backend path for the thumbnail bytes. */
@@ -764,8 +766,6 @@ export interface MobileServerSocialFeedReadSession {
764
766
  mode: MobileSocialFeedReadSessionMode;
765
767
  /** Current idle expiration in Unix milliseconds; legitimate session requests renew it. */
766
768
  expires_at_ms: number;
767
- /** Visibility requirements for explicit exposure reporting. */
768
- view_tracking: MobileServerSocialFeedViewTracking;
769
769
  /** Initial update baseline, distinct from the page cursor. */
770
770
  update_cursor: string | null;
771
771
  /** Credential for this reading session. Do not log or cache publicly. */
@@ -956,6 +956,12 @@ export interface MobileServerSocialFeedReply extends MobileServerSocialFeedItemB
956
956
  interaction: MobileServerSocialFeedInteraction;
957
957
  }
958
958
 
959
+ /** Top-level comment and all readable descendants from the same thread snapshot. */
960
+ export interface MobileServerSocialFeedCommentGroup extends MobileServerSocialFeedReply {
961
+ /** Flat descendants in creation-time, depth, then UUID order; empty when none. */
962
+ replies: MobileServerSocialFeedReply[];
963
+ }
964
+
959
965
  /** Current eligibility hints; actual writes still recheck the signed author. */
960
966
  export interface MobileServerSocialFeedPublishablePosition {
961
967
  /** Candidate position. */
@@ -15,9 +15,11 @@ import {
15
15
  MobileServerSocialFeedAuthor,
16
16
  MobileServerSocialFeedCandle,
17
17
  MobileServerSocialFeedCohort,
18
+ MobileServerSocialFeedCommentGroup,
18
19
  MobileServerSocialFeedDrawing,
19
20
  MobileServerSocialFeedEstimatedLiquidationLine,
20
21
  MobileServerSocialFeedHistorySummary,
22
+ MobileServerSocialFeedHistoryTake,
21
23
  MobileServerSocialFeedItem,
22
24
  MobileServerSocialFeedItemPatch,
23
25
  MobileServerSocialFeedMarket,
@@ -25,7 +27,6 @@ import {
25
27
  MobileServerSocialFeedPositionPatch,
26
28
  MobileServerSocialFeedPublishablePosition,
27
29
  MobileServerSocialFeedReadSession,
28
- MobileServerSocialFeedReply,
29
30
  MobileServerSocialFeedTimelineEntry,
30
31
  MobileServerSocialFeedTradingContext,
31
32
  MobileSocialFeedActionResolution,
@@ -266,6 +267,10 @@ export interface MobileServerPublicQueryResponseByType {
266
267
  feed_comments: MobileServerSocialFeedCommentsResponse;
267
268
  /** Full lifecycle history. */
268
269
  position_history: MobileServerSocialFeedPositionHistoryResponse;
270
+ /** Most recent 500 actions, without pagination or authored content. */
271
+ position_history_all: MobileServerSocialFeedPositionHistoryResponse;
272
+ /** Published nonblank text with complete drawings. */
273
+ position_thoughts: MobileServerSocialFeedPositionThoughtsResponse;
269
274
  /** Candle page and markers. */
270
275
  position_chart: MobileServerSocialFeedPositionChartResponse;
271
276
  /** Read-only watcher observation. */
@@ -354,10 +359,12 @@ export interface MobileServerSocialFeedDrawingResponse {
354
359
 
355
360
  /** Comment thread, including bidirectional pagination around an optional anchor. */
356
361
  export interface MobileServerSocialFeedCommentsResponse {
357
- /** Readable comments. */
358
- comments: MobileServerSocialFeedReply[];
359
- /** Total readable count. */
362
+ /** Top-level comments, newest first, each with all readable descendants in the snapshot. */
363
+ comments: MobileServerSocialFeedCommentGroup[];
364
+ /** All readable replies across the thread snapshot, including descendants. */
360
365
  total_count: number;
366
+ /** All readable top-level comments across the thread snapshot. */
367
+ top_level_count: number;
361
368
  /** Forward cursor. */
362
369
  next_cursor: string | null;
363
370
  /** Backward cursor. */
@@ -370,6 +377,8 @@ export interface MobileServerSocialFeedCommentsResponse {
370
377
 
371
378
  /** Full position history with additional metrics and trusted trading hints. */
372
379
  export interface MobileServerSocialFeedPositionHistoryResponse {
380
+ /** Current position owner. */
381
+ owner: MobileServerSocialFeedAuthor;
373
382
  /** Current or frozen position and history metrics. */
374
383
  position: MobileServerSocialFeedPositionHistoryCard;
375
384
  /** Counts across the authorized history. */
@@ -411,3 +420,22 @@ export interface MobileServerSocialFeedWatchersResponse {
411
420
  /** Observation time. */
412
421
  as_of_ms: number;
413
422
  }
423
+
424
+ /** Published position thought with nonblank text and a complete saved drawing. */
425
+ export type MobileServerSocialFeedPositionThought =
426
+ MobileServerSocialFeedHistoryTake & {
427
+ /** Always true for this endpoint; uploaded or external images alone do not qualify. */
428
+ has_analysis: true;
429
+ /** Parsed current drawing; saved data does not necessarily contain visible strokes. */
430
+ drawing: MobileServerSocialFeedDrawing;
431
+ };
432
+
433
+ /** Thoughts in descending drawing creation time and item UUID order; no 500-item total cap. */
434
+ export interface MobileServerSocialFeedPositionThoughtsResponse {
435
+ /** Complete content in server order; never reorder by display time. */
436
+ items: MobileServerSocialFeedPositionThought[];
437
+ /** Null only at the directory end; an empty page can still have a cursor. */
438
+ next_cursor: string | null;
439
+ /** Independent position session; newly published content requires a refresh. */
440
+ read_session: MobileServerSocialFeedReadSession;
441
+ }