@nadohq/mobile-client 0.65.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 (211) hide show
  1. package/README.md +202 -10
  2. package/dist/{MobileClient-CfVZXTcz.cjs → MobileClient-vuI5wlB_.cjs} +303 -21
  3. package/dist/MobileClient-vuI5wlB_.cjs.map +1 -0
  4. package/dist/MobileClient.cjs +1 -1
  5. package/dist/MobileClient.d.cts +175 -20
  6. package/dist/MobileClient.d.cts.map +1 -1
  7. package/dist/MobileClient.d.ts +175 -20
  8. package/dist/MobileClient.d.ts.map +1 -1
  9. package/dist/MobileClient.js +303 -21
  10. package/dist/MobileClient.js.map +1 -1
  11. package/dist/{MobileServerFailureError-BZVwlsMg.d.ts → MobileServerFailureError-BeCCNShj.d.cts} +2 -3
  12. package/dist/MobileServerFailureError-BeCCNShj.d.cts.map +1 -0
  13. package/dist/{MobileServerFailureError-CbtK8kD4.d.cts → MobileServerFailureError-hHv28weI.d.ts} +2 -3
  14. package/dist/MobileServerFailureError-hHv28weI.d.ts.map +1 -0
  15. package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts +15 -0
  16. package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts.map +1 -0
  17. package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts +15 -0
  18. package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts.map +1 -0
  19. package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts → buildSignedMobileRequest-DcEMKU_4.d.cts} +2 -2
  20. package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts.map → buildSignedMobileRequest-DcEMKU_4.d.cts.map} +1 -1
  21. package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts → buildSignedMobileRequest-DlTMvoXR.d.ts} +2 -2
  22. package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts.map → buildSignedMobileRequest-DlTMvoXR.d.ts.map} +1 -1
  23. package/dist/{canonicalize-5eeZrkzQ.d.ts → canonicalize-BOZlflI3.d.ts} +2 -2
  24. package/dist/canonicalize-BOZlflI3.d.ts.map +1 -0
  25. package/dist/{canonicalize-CBaZprTo.d.cts → canonicalize-BvCozzvJ.d.cts} +2 -2
  26. package/dist/canonicalize-BvCozzvJ.d.cts.map +1 -0
  27. package/dist/{clientTypes-_mEgzmNs.d.cts → clientTypes-KpHlCDGZ.d.cts} +341 -38
  28. package/dist/clientTypes-KpHlCDGZ.d.cts.map +1 -0
  29. package/dist/{clientTypes-C9dJufxk.d.ts → clientTypes-XRI41VdC.d.ts} +341 -38
  30. package/dist/clientTypes-XRI41VdC.d.ts.map +1 -0
  31. package/dist/dataMappers.cjs +257 -20
  32. package/dist/dataMappers.cjs.map +1 -1
  33. package/dist/dataMappers.d.cts +98 -15
  34. package/dist/dataMappers.d.cts.map +1 -1
  35. package/dist/dataMappers.d.ts +98 -15
  36. package/dist/dataMappers.d.ts.map +1 -1
  37. package/dist/dataMappers.js +242 -20
  38. package/dist/dataMappers.js.map +1 -1
  39. package/dist/{eip712MethodByType-D8jLoxDu.d.cts → eip712MethodByType-DyAmzH7b.d.cts} +2 -2
  40. package/dist/eip712MethodByType-DyAmzH7b.d.cts.map +1 -0
  41. package/dist/{eip712MethodByType-DcUh8h-l.d.ts → eip712MethodByType-qSSAF6i2.d.ts} +2 -2
  42. package/dist/eip712MethodByType-qSSAF6i2.d.ts.map +1 -0
  43. package/dist/feed-BrgkFqP3.d.cts +23 -0
  44. package/dist/feed-BrgkFqP3.d.cts.map +1 -0
  45. package/dist/feed-DcbhLoo1.d.ts +23 -0
  46. package/dist/feed-DcbhLoo1.d.ts.map +1 -0
  47. package/dist/index-BVVgDSdq.d.cts +1 -0
  48. package/dist/index-BVVgDSdq.d.ts +1 -0
  49. package/dist/index.cjs +8 -2
  50. package/dist/index.d.cts +14 -16
  51. package/dist/index.d.ts +14 -16
  52. package/dist/index.js +4 -3
  53. package/dist/{mobileErrorCodes-CmjYEIYm.d.ts → mobileErrorCodes-CdwX6QTO.d.cts} +8 -3
  54. package/dist/mobileErrorCodes-CdwX6QTO.d.cts.map +1 -0
  55. package/dist/{mobileErrorCodes-CmjYEIYm.d.cts → mobileErrorCodes-CdwX6QTO.d.ts} +8 -3
  56. package/dist/mobileErrorCodes-CdwX6QTO.d.ts.map +1 -0
  57. package/dist/{payloadHash-DhHmgRqL.d.cts → payloadHash-BgGSZock.d.cts} +2 -2
  58. package/dist/payloadHash-BgGSZock.d.cts.map +1 -0
  59. package/dist/{payloadHash-E1_8fpLW.d.ts → payloadHash-Bi6upCLQ.d.ts} +2 -2
  60. package/dist/payloadHash-Bi6upCLQ.d.ts.map +1 -0
  61. package/dist/{serverExecuteTypes-BwWGspOg.d.ts → serverExecuteTypes-DuCWe_H_.d.cts} +23 -14
  62. package/dist/serverExecuteTypes-DuCWe_H_.d.cts.map +1 -0
  63. package/dist/{serverExecuteTypes-B-OJok19.d.cts → serverExecuteTypes-usJ6HMJv.d.ts} +23 -14
  64. package/dist/serverExecuteTypes-usJ6HMJv.d.ts.map +1 -0
  65. package/dist/{serverModelTypes-CiA-dy1R.d.ts → serverModelTypes-CTHMNIQS.d.cts} +170 -13
  66. package/dist/serverModelTypes-CTHMNIQS.d.cts.map +1 -0
  67. package/dist/{serverModelTypes-CiA-dy1R.d.cts → serverModelTypes-CTHMNIQS.d.ts} +170 -13
  68. package/dist/serverModelTypes-CTHMNIQS.d.ts.map +1 -0
  69. package/dist/{serverResponseGuards-CShiXpHT.d.ts → serverResponseGuards-CzmldAad.d.cts} +2 -2
  70. package/dist/{serverResponseGuards-BW69PS7O.d.cts.map → serverResponseGuards-CzmldAad.d.cts.map} +1 -1
  71. package/dist/{serverResponseGuards-BW69PS7O.d.cts → serverResponseGuards-Dvkbjp2_.d.ts} +2 -2
  72. package/dist/{serverResponseGuards-CShiXpHT.d.ts.map → serverResponseGuards-Dvkbjp2_.d.ts.map} +1 -1
  73. package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
  74. package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
  75. package/dist/signing/canonicalize.cjs +53 -2
  76. package/dist/signing/canonicalize.cjs.map +1 -1
  77. package/dist/signing/canonicalize.d.cts +1 -1
  78. package/dist/signing/canonicalize.d.ts +1 -1
  79. package/dist/signing/canonicalize.js +53 -2
  80. package/dist/signing/canonicalize.js.map +1 -1
  81. package/dist/signing/eip712MethodByType.cjs +7 -0
  82. package/dist/signing/eip712MethodByType.cjs.map +1 -1
  83. package/dist/signing/eip712MethodByType.d.cts +1 -1
  84. package/dist/signing/eip712MethodByType.d.ts +1 -1
  85. package/dist/signing/eip712MethodByType.js +7 -0
  86. package/dist/signing/eip712MethodByType.js.map +1 -1
  87. package/dist/signing/index.d.cts +5 -5
  88. package/dist/signing/index.d.ts +5 -5
  89. package/dist/signing/payloadHash.cjs +1 -1
  90. package/dist/signing/payloadHash.d.cts +1 -1
  91. package/dist/signing/payloadHash.d.ts +1 -1
  92. package/dist/signing/payloadHash.js +1 -1
  93. package/dist/signing/types.d.cts +1 -1
  94. package/dist/signing/types.d.ts +1 -1
  95. package/dist/types/MobileServerFailureError.d.cts +1 -1
  96. package/dist/types/MobileServerFailureError.d.ts +1 -1
  97. package/dist/types/MobileUnsupportedNotificationRouteError.cjs +19 -0
  98. package/dist/types/MobileUnsupportedNotificationRouteError.cjs.map +1 -0
  99. package/dist/types/MobileUnsupportedNotificationRouteError.d.cts +2 -0
  100. package/dist/types/MobileUnsupportedNotificationRouteError.d.ts +2 -0
  101. package/dist/types/MobileUnsupportedNotificationRouteError.js +18 -0
  102. package/dist/types/MobileUnsupportedNotificationRouteError.js.map +1 -0
  103. package/dist/types/clientTypes.d.cts +2 -2
  104. package/dist/types/clientTypes.d.ts +2 -2
  105. package/dist/types/index.cjs +4 -0
  106. package/dist/types/index.d.cts +8 -9
  107. package/dist/types/index.d.ts +8 -9
  108. package/dist/types/index.js +3 -2
  109. package/dist/types/mobileErrorCodes.cjs +11 -1
  110. package/dist/types/mobileErrorCodes.cjs.map +1 -1
  111. package/dist/types/mobileErrorCodes.d.cts +1 -1
  112. package/dist/types/mobileErrorCodes.d.ts +1 -1
  113. package/dist/types/mobileErrorCodes.js +11 -1
  114. package/dist/types/mobileErrorCodes.js.map +1 -1
  115. package/dist/types/serverBaseTypes.d.cts +1 -1
  116. package/dist/types/serverBaseTypes.d.ts +1 -1
  117. package/dist/types/serverExecuteTypes.d.cts +2 -2
  118. package/dist/types/serverExecuteTypes.d.ts +2 -2
  119. package/dist/types/serverModelTypes.cjs +15 -0
  120. package/dist/types/serverModelTypes.cjs.map +1 -1
  121. package/dist/types/serverModelTypes.d.cts +2 -2
  122. package/dist/types/serverModelTypes.d.ts +2 -2
  123. package/dist/types/serverModelTypes.js +14 -1
  124. package/dist/types/serverModelTypes.js.map +1 -1
  125. package/dist/types/serverQueryTypes.d.cts +2 -2
  126. package/dist/types/serverQueryTypes.d.ts +2 -2
  127. package/dist/types/serverRequestTypes.d.cts +2 -2
  128. package/dist/types/serverRequestTypes.d.ts +2 -2
  129. package/dist/types-CByl_NyJ.d.ts +825 -0
  130. package/dist/types-CByl_NyJ.d.ts.map +1 -0
  131. package/dist/types-CR5iaKR4.d.cts +825 -0
  132. package/dist/types-CR5iaKR4.d.cts.map +1 -0
  133. package/dist/utils/buildMobileFeedImageMultipart.cjs +31 -0
  134. package/dist/utils/buildMobileFeedImageMultipart.cjs.map +1 -0
  135. package/dist/utils/buildMobileFeedImageMultipart.d.cts +15 -0
  136. package/dist/utils/buildMobileFeedImageMultipart.d.cts.map +1 -0
  137. package/dist/utils/buildMobileFeedImageMultipart.d.ts +15 -0
  138. package/dist/utils/buildMobileFeedImageMultipart.d.ts.map +1 -0
  139. package/dist/utils/buildMobileFeedImageMultipart.js +30 -0
  140. package/dist/utils/buildMobileFeedImageMultipart.js.map +1 -0
  141. package/dist/utils/feed.cjs +12 -0
  142. package/dist/utils/feed.cjs.map +1 -1
  143. package/dist/utils/feed.d.cts +2 -2
  144. package/dist/utils/feed.d.ts +2 -2
  145. package/dist/utils/feed.js +11 -1
  146. package/dist/utils/feed.js.map +1 -1
  147. package/dist/utils/index.cjs +3 -1
  148. package/dist/utils/index.d.cts +3 -3
  149. package/dist/utils/index.d.ts +3 -3
  150. package/dist/utils/index.js +2 -2
  151. package/dist/utils/serverResponseGuards.d.cts +1 -1
  152. package/dist/utils/serverResponseGuards.d.ts +1 -1
  153. package/package.json +3 -3
  154. package/src/MobileClient.ts +444 -19
  155. package/src/dataMappers.test.ts +511 -6
  156. package/src/dataMappers.ts +325 -24
  157. package/src/feedImages.test.ts +309 -0
  158. package/src/notifications.test.ts +950 -0
  159. package/src/signing/canonicalize.ts +72 -0
  160. package/src/signing/eip712MethodByType.ts +10 -0
  161. package/src/signing/signing.test.ts +67 -1
  162. package/src/signing/types.ts +55 -9
  163. package/src/socialFeed.test.ts +63 -2
  164. package/src/types/MobileUnsupportedNotificationRouteError.ts +16 -0
  165. package/src/types/clientTypes.ts +427 -36
  166. package/src/types/index.ts +1 -0
  167. package/src/types/mobileErrorCodes.ts +10 -0
  168. package/src/types/serverExecuteTypes.ts +22 -10
  169. package/src/types/serverModelTypes.ts +216 -11
  170. package/src/types/serverQueryTypes.ts +106 -9
  171. package/src/types/serverRequestTypes.ts +114 -17
  172. package/src/utils/buildMobileFeedImageMultipart.test.ts +45 -0
  173. package/src/utils/buildMobileFeedImageMultipart.ts +36 -0
  174. package/src/utils/feed.ts +16 -0
  175. package/dist/MobileClient-CfVZXTcz.cjs.map +0 -1
  176. package/dist/MobileServerFailureError-BZVwlsMg.d.ts.map +0 -1
  177. package/dist/MobileServerFailureError-CbtK8kD4.d.cts.map +0 -1
  178. package/dist/canonicalize-5eeZrkzQ.d.ts.map +0 -1
  179. package/dist/canonicalize-CBaZprTo.d.cts.map +0 -1
  180. package/dist/clientTypes-C9dJufxk.d.ts.map +0 -1
  181. package/dist/clientTypes-_mEgzmNs.d.cts.map +0 -1
  182. package/dist/eip712MethodByType-D8jLoxDu.d.cts.map +0 -1
  183. package/dist/eip712MethodByType-DcUh8h-l.d.ts.map +0 -1
  184. package/dist/feed-DTJlsg8W.d.cts +0 -14
  185. package/dist/feed-DTJlsg8W.d.cts.map +0 -1
  186. package/dist/feed-DTJlsg8W.d.ts +0 -14
  187. package/dist/feed-DTJlsg8W.d.ts.map +0 -1
  188. package/dist/mobileErrorCodes-CmjYEIYm.d.cts.map +0 -1
  189. package/dist/mobileErrorCodes-CmjYEIYm.d.ts.map +0 -1
  190. package/dist/payloadHash-DhHmgRqL.d.cts.map +0 -1
  191. package/dist/payloadHash-E1_8fpLW.d.ts.map +0 -1
  192. package/dist/serverBaseTypes-B5J1_TyC.d.cts +0 -25
  193. package/dist/serverBaseTypes-B5J1_TyC.d.cts.map +0 -1
  194. package/dist/serverBaseTypes-DnLzsdm-.d.ts +0 -25
  195. package/dist/serverBaseTypes-DnLzsdm-.d.ts.map +0 -1
  196. package/dist/serverExecuteTypes-B-OJok19.d.cts.map +0 -1
  197. package/dist/serverExecuteTypes-BwWGspOg.d.ts.map +0 -1
  198. package/dist/serverModelTypes-CiA-dy1R.d.cts.map +0 -1
  199. package/dist/serverModelTypes-CiA-dy1R.d.ts.map +0 -1
  200. package/dist/serverQueryTypes-CJcjLB7n.d.ts +0 -283
  201. package/dist/serverQueryTypes-CJcjLB7n.d.ts.map +0 -1
  202. package/dist/serverQueryTypes-V1f9yE7L.d.cts +0 -283
  203. package/dist/serverQueryTypes-V1f9yE7L.d.cts.map +0 -1
  204. package/dist/serverRequestTypes-CRlkNSu5.d.cts +0 -270
  205. package/dist/serverRequestTypes-CRlkNSu5.d.cts.map +0 -1
  206. package/dist/serverRequestTypes-DZoOhV2o.d.ts +0 -270
  207. package/dist/serverRequestTypes-DZoOhV2o.d.ts.map +0 -1
  208. package/dist/types-BiRmH5Iv.d.cts +0 -108
  209. package/dist/types-BiRmH5Iv.d.cts.map +0 -1
  210. package/dist/types-DoJuq0Ij.d.ts +0 -108
  211. package/dist/types-DoJuq0Ij.d.ts.map +0 -1
@@ -12,6 +12,60 @@ export function canonicalizeMobileInner(
12
12
  inner: MobileSignedInner,
13
13
  ): MobileSignedInner {
14
14
  switch (inner.type) {
15
+ case 'register_expo_token_v2':
16
+ return {
17
+ type: inner.type,
18
+ expo_token: inner.expo_token,
19
+ platform: inner.platform,
20
+ locale: inner.locale,
21
+ app_version: inner.app_version,
22
+ capabilities: {
23
+ payload_versions: [...inner.capabilities.payload_versions],
24
+ type_ids: [...inner.capabilities.type_ids],
25
+ routes: [...inner.capabilities.routes],
26
+ },
27
+ };
28
+ case 'notification_subscriptions':
29
+ return { type: inner.type };
30
+ case 'update_notification_subscriptions':
31
+ return {
32
+ type: inner.type,
33
+ expected_revision: inner.expected_revision,
34
+ trader_bell:
35
+ inner.trader_bell == null
36
+ ? null
37
+ : {
38
+ subaccount: inner.trader_bell.subaccount,
39
+ enabled: inner.trader_bell.enabled,
40
+ },
41
+ market:
42
+ inner.market == null
43
+ ? null
44
+ : {
45
+ product_id: inner.market.product_id,
46
+ subscribed: inner.market.subscribed,
47
+ },
48
+ import_markets:
49
+ inner.import_markets == null ? null : [...inner.import_markets],
50
+ };
51
+ case 'notification_inbox_v2':
52
+ return {
53
+ type: inner.type,
54
+ cursor: inner.cursor,
55
+ limit: inner.limit,
56
+ };
57
+ case 'record_product_events':
58
+ return {
59
+ type: inner.type,
60
+ events: inner.events.map((event) => ({
61
+ client_event_id: event.client_event_id,
62
+ event_name: event.event_name,
63
+ notification_id: event.notification_id,
64
+ occurred_at: event.occurred_at,
65
+ product_id: event.product_id,
66
+ query: event.query,
67
+ })),
68
+ };
15
69
  case 'create_feed_post':
16
70
  return {
17
71
  type: inner.type,
@@ -25,6 +79,12 @@ export function canonicalizeMobileInner(
25
79
  ...(inner.drawing_data_json === undefined
26
80
  ? {}
27
81
  : { drawing_data_json: inner.drawing_data_json }),
82
+ ...(inner.image_ids === undefined
83
+ ? {}
84
+ : { image_ids: [...inner.image_ids] }),
85
+ ...(inner.image_urls === undefined
86
+ ? {}
87
+ : { image_urls: [...inner.image_urls] }),
28
88
  };
29
89
  case 'create_feed_comment':
30
90
  return {
@@ -39,6 +99,18 @@ export function canonicalizeMobileInner(
39
99
  ...(inner.attached_position_id === undefined
40
100
  ? {}
41
101
  : { attached_position_id: inner.attached_position_id }),
102
+ ...(inner.image_ids === undefined
103
+ ? {}
104
+ : { image_ids: [...inner.image_ids] }),
105
+ ...(inner.image_urls === undefined
106
+ ? {}
107
+ : { image_urls: [...inner.image_urls] }),
108
+ };
109
+ case 'create_feed_post_image_uploads':
110
+ case 'create_feed_comment_image_uploads':
111
+ return {
112
+ type: inner.type,
113
+ content_hash: inner.content_hash,
42
114
  };
43
115
  case 'create_feed_chart_drawing':
44
116
  case 'update_feed_chart_drawing':
@@ -7,8 +7,18 @@ export const MOBILE_EIP712_METHOD_BY_TYPE: Record<
7
7
  MobileSignedInner['type'],
8
8
  string
9
9
  > = {
10
+ register_expo_token_v2: 'mobile:execute_register_expo_token_v2',
11
+ notification_subscriptions: 'mobile:query_notification_subscriptions',
12
+ update_notification_subscriptions:
13
+ 'mobile:execute_update_notification_subscriptions',
14
+ notification_inbox_v2: 'mobile:query_notification_inbox_v2',
15
+ record_product_events: 'mobile:execute_record_product_events',
10
16
  create_feed_post: 'mobile:execute_create_feed_post',
11
17
  create_feed_comment: 'mobile:execute_create_feed_comment',
18
+ create_feed_post_image_uploads:
19
+ 'mobile:execute_create_feed_post_image_uploads',
20
+ create_feed_comment_image_uploads:
21
+ 'mobile:execute_create_feed_comment_image_uploads',
12
22
  create_feed_chart_drawing: 'mobile:execute_create_feed_chart_drawing',
13
23
  update_feed_chart_drawing: 'mobile:execute_update_feed_chart_drawing',
14
24
  set_feed_like: 'mobile:execute_set_feed_like',
@@ -435,7 +435,9 @@ describe('[mobile-client]: social content signing', () => {
435
435
  const drawing = ' { "lines" : [], "enabled": false } ';
436
436
 
437
437
  it('canonicalizes every post field and nested mention without changing content', () => {
438
+ const imageIds = ['88888888-8888-4888-8888-888888888888'];
438
439
  const input: MobileSignedInner = {
440
+ image_ids: imageIds,
439
441
  drawing_data_json: drawing,
440
442
  mentions: [mention],
441
443
  text,
@@ -453,6 +455,7 @@ describe('[mobile-client]: social content signing', () => {
453
455
  'text',
454
456
  'mentions',
455
457
  'drawing_data_json',
458
+ 'image_ids',
456
459
  ]);
457
460
  if (result.type !== 'create_feed_post') throw new Error('Expected post');
458
461
  expect(Object.keys(result.mentions[0])).toEqual([
@@ -466,6 +469,8 @@ describe('[mobile-client]: social content signing', () => {
466
469
  expect(result.position_id).toBe(positionId);
467
470
  expect(result.text).toBe(text);
468
471
  expect(result.drawing_data_json).toBe(drawing);
472
+ expect(result.image_ids).toEqual(imageIds);
473
+ expect(result.image_ids).not.toBe(imageIds);
469
474
  expect(result.mentions[0]).toEqual({
470
475
  subaccount: FIXTURE_TARGET_SUBACCOUNT,
471
476
  start: 2,
@@ -582,6 +587,16 @@ describe('[mobile-client]: social content signing', () => {
582
587
  });
583
588
 
584
589
  const payloads: MobileSignedInner[] = [
590
+ {
591
+ type: 'create_feed_post_image_uploads',
592
+ content_hash:
593
+ '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
594
+ },
595
+ {
596
+ type: 'create_feed_comment_image_uploads',
597
+ content_hash:
598
+ '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
599
+ },
585
600
  {
586
601
  type: 'create_feed_post',
587
602
  client_request_id: requestId,
@@ -633,8 +648,59 @@ describe('[mobile-client]: social content signing', () => {
633
648
  signature: signed.signature,
634
649
  });
635
650
  expect(recovered.toLowerCase()).toBe(account.address.toLowerCase());
636
- expect(signed).toMatchObject(canonical);
651
+ expect(signed).toMatchObject({ ...canonical });
637
652
  expect(signed).not.toHaveProperty('payload');
638
653
  },
639
654
  );
640
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
+ });
@@ -2,10 +2,15 @@ import { WalletClientWithAccount } from '@nadohq/shared';
2
2
  import { Hex } from 'viem';
3
3
  import { MobileSignedRequestParams } from '../types/clientTypes';
4
4
  import {
5
- MobileNotificationPlatform,
6
5
  MobileServerAvatarSelection,
6
+ MobileServerProductEvent,
7
7
  MobileSocialFeedMention,
8
8
  } from '../types/serverModelTypes';
9
+ import {
10
+ MobileServerNotificationSubscriptionsUpdate,
11
+ MobileServerRegisterExpoTokenParams,
12
+ MobileServerRegisterExpoTokenV2Params,
13
+ } from '../types/serverRequestTypes';
9
14
 
10
15
  /**
11
16
  * Unsigned inner payloads that can be authenticated against the mobile service API.
@@ -22,6 +27,32 @@ import {
22
27
  * lowercases; unlike bytes32, string fields such as `display_name` are hashed as-is and keep their casing.
23
28
  */
24
29
  export type MobileSignedInner =
30
+ | ({
31
+ /** Capability-aware device registration. */
32
+ type: 'register_expo_token_v2';
33
+ } & MobileServerRegisterExpoTokenV2Params)
34
+ | {
35
+ /** Signed query for wallet markets and Identity bells. */
36
+ type: 'notification_subscriptions';
37
+ }
38
+ | ({
39
+ /** One revision-guarded subscription operation. */
40
+ type: 'update_notification_subscriptions';
41
+ } & MobileServerNotificationSubscriptionsUpdate)
42
+ | {
43
+ /** Signed query for the wallet's private notification page. */
44
+ type: 'notification_inbox_v2';
45
+ /** Previous page cursor, or nil for the first page. */
46
+ cursor: string | null;
47
+ /** Page size, or nil for the server default. */
48
+ limit: number | null;
49
+ }
50
+ | {
51
+ /** Atomic product analytics batch. */
52
+ type: 'record_product_events';
53
+ /** Events in caller order. */
54
+ events: MobileServerProductEvent[];
55
+ }
25
56
  | {
26
57
  /** Atomic post publication. */
27
58
  type: 'create_feed_post';
@@ -37,6 +68,10 @@ export type MobileSignedInner =
37
68
  mentions: MobileSocialFeedMention[];
38
69
  /** Original JSON text; omitted when absent. */
39
70
  drawing_data_json?: string;
71
+ /** Ordered ready image UUIDs; omitted when absent. */
72
+ image_ids?: string[];
73
+ /** Up to ten original HTTP/HTTPS URLs in order; omission differs from an empty array. */
74
+ image_urls?: string[];
40
75
  }
41
76
  | {
42
77
  /** Atomic comment publication. */
@@ -53,6 +88,22 @@ export type MobileSignedInner =
53
88
  mentions: MobileSocialFeedMention[];
54
89
  /** Optional owned position UUID. */
55
90
  attached_position_id?: string;
91
+ /** Ordered ready image UUIDs; omitted when absent. */
92
+ image_ids?: string[];
93
+ /** Up to ten original HTTP/HTTPS URLs in order; omission differs from an empty array. */
94
+ image_urls?: string[];
95
+ }
96
+ | {
97
+ /** Direct multipart post-image upload; content_hash is Keccak256 of the raw file bytes. */
98
+ type: 'create_feed_post_image_uploads';
99
+ /** `0x`-prefixed lowercase Keccak256 of the original file bytes. */
100
+ content_hash: Hex;
101
+ }
102
+ | {
103
+ /** Direct multipart comment-image upload; content_hash is Keccak256 of the raw file bytes. */
104
+ type: 'create_feed_comment_image_uploads';
105
+ /** `0x`-prefixed lowercase Keccak256 of the original file bytes. */
106
+ content_hash: Hex;
56
107
  }
57
108
  | {
58
109
  /** First attachment. */
@@ -86,15 +137,10 @@ export type MobileSignedInner =
86
137
  }
87
138
  | { type: 'set_username'; display_name: string }
88
139
  | { type: 'set_private_mode'; private_mode: boolean }
89
- | {
140
+ | ({
141
+ /** Legacy device registration without a capability declaration. */
90
142
  type: 'register_expo_token';
91
- expo_token: string;
92
- platform: MobileNotificationPlatform;
93
- // Optional on the wire, but always present (null when unset) so the msgpack payload hash matches the
94
- // backend, which serializes `Option::None` as nil under the same key.
95
- locale: string | null;
96
- app_version: string | null;
97
- }
143
+ } & MobileServerRegisterExpoTokenParams)
98
144
  | {
99
145
  type: 'set_follow';
100
146
  subaccount: Hex;
@@ -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: { min_visible_ratio: 0.5, min_duration_ms: 1000 },
85
+
86
86
  update_cursor: null,
87
87
  viewer_token: 'OpaqueViewer',
88
88
  };
@@ -222,6 +222,7 @@ describe('[mobile-client]: social feed transport', () => {
222
222
  expect(requests[3].body.subaccount).not.toBe(requests[3].body.view_as);
223
223
  expect(requests[5].body.position_id).toBe(positionId.toUpperCase());
224
224
  expect(requests[6].body.item_id).toBe(itemId.toUpperCase());
225
+ expect(requests[6].body).not.toHaveProperty('view_event_id');
225
226
  expect(requests[6].body.comment_preview_limit).toBe(0);
226
227
  expect(requests[7].body).toEqual({
227
228
  type: 'feed_updates',
@@ -235,6 +236,65 @@ describe('[mobile-client]: social feed transport', () => {
235
236
  },
236
237
  );
237
238
 
239
+ it('reports multiple IDs and same-request duplicates without session fields or a signature', async () => {
240
+ const { client, requests } = mockClient({ status: 'success' });
241
+ client.opts.walletClient = undefined;
242
+ const itemIds = [itemId, 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', itemId];
243
+ expect(await client.recordFeedViews({ itemIds })).toEqual({
244
+ status: 'success',
245
+ });
246
+ expect(requests).toEqual([
247
+ {
248
+ url: 'https://mobile.example/v1/mobile/public_execute',
249
+ body: { type: 'record_feed_views', item_ids: itemIds },
250
+ },
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);
296
+ });
297
+
238
298
  it('preserves update IDs, session credentials, and cursors unchanged', async () => {
239
299
  const { client, requests } = mockClient();
240
300
  client.opts.walletClient = undefined;
@@ -309,7 +369,7 @@ describe('[mobile-client]: social feed transport', () => {
309
369
  session_id: 'Session',
310
370
  mode: 'feed',
311
371
  expires_at_ms: 42,
312
- view_tracking: { min_visible_ratio: 0.5, min_duration_ms: 1000 },
372
+
313
373
  update_cursor: null,
314
374
  viewer_token: 'Viewer',
315
375
  },
@@ -660,6 +720,7 @@ describe('[mobile-client]: social feed response mapping', () => {
660
720
  ...row,
661
721
  kind: 'trade_card',
662
722
  action: 'opened',
723
+ images: [],
663
724
  text: '',
664
725
  mentions: [],
665
726
  author: {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Error thrown when the mobile service returns a notification route this SDK does not support.
3
+ */
4
+ export class MobileUnsupportedNotificationRouteError extends Error {
5
+ /** Unrecognized route name returned by the service. */
6
+ readonly routeName: string;
7
+
8
+ /**
9
+ * @param routeName - The unrecognized service route name.
10
+ */
11
+ constructor(routeName: string) {
12
+ super(`Unsupported mobile notification route "${routeName}".`);
13
+ this.name = 'MobileUnsupportedNotificationRouteError';
14
+ this.routeName = routeName;
15
+ }
16
+ }