@nadohq/mobile-client 0.65.0 → 0.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -8
- package/dist/{MobileClient-CfVZXTcz.cjs → MobileClient-M4oC4lrX.cjs} +258 -16
- package/dist/MobileClient-M4oC4lrX.cjs.map +1 -0
- package/dist/MobileClient.cjs +1 -1
- package/dist/MobileClient.d.cts +146 -16
- package/dist/MobileClient.d.cts.map +1 -1
- package/dist/MobileClient.d.ts +146 -16
- package/dist/MobileClient.d.ts.map +1 -1
- package/dist/MobileClient.js +258 -16
- package/dist/MobileClient.js.map +1 -1
- package/dist/{MobileServerFailureError-BZVwlsMg.d.ts → MobileServerFailureError-C9iqNsxI.d.ts} +2 -3
- package/dist/MobileServerFailureError-C9iqNsxI.d.ts.map +1 -0
- package/dist/{MobileServerFailureError-CbtK8kD4.d.cts → MobileServerFailureError-MtS2reel.d.cts} +2 -3
- package/dist/MobileServerFailureError-MtS2reel.d.cts.map +1 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts +15 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts.map +1 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts +15 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts.map +1 -0
- package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts → buildSignedMobileRequest-BR-KJhIF.d.ts} +2 -2
- package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts.map → buildSignedMobileRequest-BR-KJhIF.d.ts.map} +1 -1
- package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts → buildSignedMobileRequest-DyKCK34Y.d.cts} +2 -2
- package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts.map → buildSignedMobileRequest-DyKCK34Y.d.cts.map} +1 -1
- package/dist/{canonicalize-5eeZrkzQ.d.ts → canonicalize-Bv6UIAbR.d.ts} +2 -2
- package/dist/canonicalize-Bv6UIAbR.d.ts.map +1 -0
- package/dist/{canonicalize-CBaZprTo.d.cts → canonicalize-CWh3XZzj.d.cts} +2 -2
- package/dist/canonicalize-CWh3XZzj.d.cts.map +1 -0
- package/dist/{clientTypes-C9dJufxk.d.ts → clientTypes-BUn5_OzK.d.ts} +297 -20
- package/dist/clientTypes-BUn5_OzK.d.ts.map +1 -0
- package/dist/{clientTypes-_mEgzmNs.d.cts → clientTypes-DOpUcu80.d.cts} +297 -20
- package/dist/clientTypes-DOpUcu80.d.cts.map +1 -0
- package/dist/dataMappers.cjs +203 -5
- package/dist/dataMappers.cjs.map +1 -1
- package/dist/dataMappers.d.cts +70 -7
- package/dist/dataMappers.d.cts.map +1 -1
- package/dist/dataMappers.d.ts +70 -7
- package/dist/dataMappers.d.ts.map +1 -1
- package/dist/dataMappers.js +192 -6
- package/dist/dataMappers.js.map +1 -1
- package/dist/{eip712MethodByType-D8jLoxDu.d.cts → eip712MethodByType-CJ6BoayT.d.cts} +2 -2
- package/dist/eip712MethodByType-CJ6BoayT.d.cts.map +1 -0
- package/dist/{eip712MethodByType-DcUh8h-l.d.ts → eip712MethodByType-Dx4mvgni.d.ts} +2 -2
- package/dist/eip712MethodByType-Dx4mvgni.d.ts.map +1 -0
- package/dist/{feed-DTJlsg8W.d.ts → feed-DErOLaoY.d.cts} +4 -2
- package/dist/feed-DErOLaoY.d.cts.map +1 -0
- package/dist/{feed-DTJlsg8W.d.cts → feed-DErOLaoY.d.ts} +4 -2
- package/dist/feed-DErOLaoY.d.ts.map +1 -0
- package/dist/index.cjs +7 -2
- package/dist/index.d.cts +14 -16
- package/dist/index.d.ts +14 -16
- package/dist/index.js +4 -3
- package/dist/{mobileErrorCodes-CmjYEIYm.d.ts → mobileErrorCodes-vD6rbFuF.d.cts} +8 -3
- package/dist/mobileErrorCodes-vD6rbFuF.d.cts.map +1 -0
- package/dist/{mobileErrorCodes-CmjYEIYm.d.cts → mobileErrorCodes-vD6rbFuF.d.ts} +8 -3
- package/dist/mobileErrorCodes-vD6rbFuF.d.ts.map +1 -0
- package/dist/{payloadHash-DhHmgRqL.d.cts → payloadHash-CdkF2Ocr.d.cts} +2 -2
- package/dist/payloadHash-CdkF2Ocr.d.cts.map +1 -0
- package/dist/{payloadHash-E1_8fpLW.d.ts → payloadHash-HD29K77M.d.ts} +2 -2
- package/dist/payloadHash-HD29K77M.d.ts.map +1 -0
- package/dist/{serverExecuteTypes-B-OJok19.d.cts → serverExecuteTypes-5JyyIXRK.d.ts} +21 -5
- package/dist/serverExecuteTypes-5JyyIXRK.d.ts.map +1 -0
- package/dist/{serverExecuteTypes-BwWGspOg.d.ts → serverExecuteTypes-CHrQ9Rtq.d.cts} +21 -5
- package/dist/serverExecuteTypes-CHrQ9Rtq.d.cts.map +1 -0
- package/dist/{serverModelTypes-CiA-dy1R.d.ts → serverModelTypes-CGUZCi0y.d.cts} +163 -8
- package/dist/serverModelTypes-CGUZCi0y.d.cts.map +1 -0
- package/dist/{serverModelTypes-CiA-dy1R.d.cts → serverModelTypes-CGUZCi0y.d.ts} +163 -8
- package/dist/serverModelTypes-CGUZCi0y.d.ts.map +1 -0
- package/dist/{serverResponseGuards-CShiXpHT.d.ts → serverResponseGuards-Bvw8ltYJ.d.ts} +2 -2
- package/dist/{serverResponseGuards-CShiXpHT.d.ts.map → serverResponseGuards-Bvw8ltYJ.d.ts.map} +1 -1
- package/dist/{serverResponseGuards-BW69PS7O.d.cts → serverResponseGuards-i1njcYKN.d.cts} +2 -2
- package/dist/{serverResponseGuards-BW69PS7O.d.cts.map → serverResponseGuards-i1njcYKN.d.cts.map} +1 -1
- package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
- package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
- package/dist/signing/canonicalize.cjs +51 -2
- package/dist/signing/canonicalize.cjs.map +1 -1
- package/dist/signing/canonicalize.d.cts +1 -1
- package/dist/signing/canonicalize.d.ts +1 -1
- package/dist/signing/canonicalize.js +51 -2
- package/dist/signing/canonicalize.js.map +1 -1
- package/dist/signing/eip712MethodByType.cjs +7 -0
- package/dist/signing/eip712MethodByType.cjs.map +1 -1
- package/dist/signing/eip712MethodByType.d.cts +1 -1
- package/dist/signing/eip712MethodByType.d.ts +1 -1
- package/dist/signing/eip712MethodByType.js +7 -0
- package/dist/signing/eip712MethodByType.js.map +1 -1
- package/dist/signing/index.d.cts +5 -5
- package/dist/signing/index.d.ts +5 -5
- package/dist/signing/payloadHash.cjs +1 -1
- package/dist/signing/payloadHash.d.cts +1 -1
- package/dist/signing/payloadHash.d.ts +1 -1
- package/dist/signing/payloadHash.js +1 -1
- package/dist/signing/types.d.cts +1 -1
- package/dist/signing/types.d.ts +1 -1
- package/dist/types/MobileServerFailureError.d.cts +1 -1
- package/dist/types/MobileServerFailureError.d.ts +1 -1
- package/dist/types/MobileUnsupportedNotificationRouteError.cjs +19 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.cjs.map +1 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.d.cts +2 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.d.ts +2 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.js +18 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.js.map +1 -0
- package/dist/types/clientTypes.d.cts +2 -2
- package/dist/types/clientTypes.d.ts +2 -2
- package/dist/types/index.cjs +4 -0
- package/dist/types/index.d.cts +8 -9
- package/dist/types/index.d.ts +8 -9
- package/dist/types/index.js +3 -2
- package/dist/types/mobileErrorCodes.cjs +11 -1
- package/dist/types/mobileErrorCodes.cjs.map +1 -1
- package/dist/types/mobileErrorCodes.d.cts +1 -1
- package/dist/types/mobileErrorCodes.d.ts +1 -1
- package/dist/types/mobileErrorCodes.js +11 -1
- package/dist/types/mobileErrorCodes.js.map +1 -1
- package/dist/types/serverBaseTypes.d.cts +1 -1
- package/dist/types/serverBaseTypes.d.ts +1 -1
- package/dist/types/serverExecuteTypes.d.cts +2 -2
- package/dist/types/serverExecuteTypes.d.ts +2 -2
- package/dist/types/serverModelTypes.cjs +15 -0
- package/dist/types/serverModelTypes.cjs.map +1 -1
- package/dist/types/serverModelTypes.d.cts +2 -2
- package/dist/types/serverModelTypes.d.ts +2 -2
- package/dist/types/serverModelTypes.js +14 -1
- package/dist/types/serverModelTypes.js.map +1 -1
- package/dist/types/serverQueryTypes.d.cts +2 -2
- package/dist/types/serverQueryTypes.d.ts +2 -2
- package/dist/types/serverRequestTypes.d.cts +2 -2
- package/dist/types/serverRequestTypes.d.ts +2 -2
- package/dist/types-C9YE4EIt.d.cts +794 -0
- package/dist/types-C9YE4EIt.d.cts.map +1 -0
- package/dist/types-Cwy1y_9x.d.ts +794 -0
- package/dist/types-Cwy1y_9x.d.ts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.cjs +31 -0
- package/dist/utils/buildMobileFeedImageMultipart.cjs.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.cts +15 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.cts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.ts +15 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.ts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.js +30 -0
- package/dist/utils/buildMobileFeedImageMultipart.js.map +1 -0
- package/dist/utils/feed.cjs +3 -0
- package/dist/utils/feed.cjs.map +1 -1
- package/dist/utils/feed.d.cts +2 -2
- package/dist/utils/feed.d.ts +2 -2
- package/dist/utils/feed.js +3 -1
- package/dist/utils/feed.js.map +1 -1
- package/dist/utils/index.cjs +2 -1
- package/dist/utils/index.d.cts +3 -3
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +2 -2
- package/dist/utils/serverResponseGuards.d.cts +1 -1
- package/dist/utils/serverResponseGuards.d.ts +1 -1
- package/package.json +3 -3
- package/src/MobileClient.ts +388 -11
- package/src/dataMappers.test.ts +108 -2
- package/src/dataMappers.ts +253 -5
- package/src/feedImages.test.ts +309 -0
- package/src/notifications.test.ts +950 -0
- package/src/signing/canonicalize.ts +66 -0
- package/src/signing/eip712MethodByType.ts +10 -0
- package/src/signing/signing.test.ts +16 -1
- package/src/signing/types.ts +51 -9
- package/src/socialFeed.test.ts +38 -2
- package/src/types/MobileUnsupportedNotificationRouteError.ts +16 -0
- package/src/types/clientTypes.ts +372 -16
- package/src/types/index.ts +1 -0
- package/src/types/mobileErrorCodes.ts +10 -0
- package/src/types/serverExecuteTypes.ts +20 -0
- package/src/types/serverModelTypes.ts +205 -6
- package/src/types/serverQueryTypes.ts +74 -5
- package/src/types/serverRequestTypes.ts +101 -12
- package/src/utils/buildMobileFeedImageMultipart.test.ts +45 -0
- package/src/utils/buildMobileFeedImageMultipart.ts +36 -0
- package/src/utils/feed.ts +3 -0
- package/dist/MobileClient-CfVZXTcz.cjs.map +0 -1
- package/dist/MobileServerFailureError-BZVwlsMg.d.ts.map +0 -1
- package/dist/MobileServerFailureError-CbtK8kD4.d.cts.map +0 -1
- package/dist/canonicalize-5eeZrkzQ.d.ts.map +0 -1
- package/dist/canonicalize-CBaZprTo.d.cts.map +0 -1
- package/dist/clientTypes-C9dJufxk.d.ts.map +0 -1
- package/dist/clientTypes-_mEgzmNs.d.cts.map +0 -1
- package/dist/eip712MethodByType-D8jLoxDu.d.cts.map +0 -1
- package/dist/eip712MethodByType-DcUh8h-l.d.ts.map +0 -1
- package/dist/feed-DTJlsg8W.d.cts.map +0 -1
- package/dist/feed-DTJlsg8W.d.ts.map +0 -1
- package/dist/mobileErrorCodes-CmjYEIYm.d.cts.map +0 -1
- package/dist/mobileErrorCodes-CmjYEIYm.d.ts.map +0 -1
- package/dist/payloadHash-DhHmgRqL.d.cts.map +0 -1
- package/dist/payloadHash-E1_8fpLW.d.ts.map +0 -1
- package/dist/serverBaseTypes-B5J1_TyC.d.cts +0 -25
- package/dist/serverBaseTypes-B5J1_TyC.d.cts.map +0 -1
- package/dist/serverBaseTypes-DnLzsdm-.d.ts +0 -25
- package/dist/serverBaseTypes-DnLzsdm-.d.ts.map +0 -1
- package/dist/serverExecuteTypes-B-OJok19.d.cts.map +0 -1
- package/dist/serverExecuteTypes-BwWGspOg.d.ts.map +0 -1
- package/dist/serverModelTypes-CiA-dy1R.d.cts.map +0 -1
- package/dist/serverModelTypes-CiA-dy1R.d.ts.map +0 -1
- package/dist/serverQueryTypes-CJcjLB7n.d.ts +0 -283
- package/dist/serverQueryTypes-CJcjLB7n.d.ts.map +0 -1
- package/dist/serverQueryTypes-V1f9yE7L.d.cts +0 -283
- package/dist/serverQueryTypes-V1f9yE7L.d.cts.map +0 -1
- package/dist/serverRequestTypes-CRlkNSu5.d.cts +0 -270
- package/dist/serverRequestTypes-CRlkNSu5.d.cts.map +0 -1
- package/dist/serverRequestTypes-DZoOhV2o.d.ts +0 -270
- package/dist/serverRequestTypes-DZoOhV2o.d.ts.map +0 -1
- package/dist/types-BiRmH5Iv.d.cts +0 -108
- package/dist/types-BiRmH5Iv.d.cts.map +0 -1
- package/dist/types-DoJuq0Ij.d.ts +0 -108
- package/dist/types-DoJuq0Ij.d.ts.map +0 -1
|
@@ -33,6 +33,51 @@ export interface MobileServerAvatarPreset {
|
|
|
33
33
|
*/
|
|
34
34
|
export type MobileNotificationPlatform = 'ios' | 'android';
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Notification types supported by the v2 payload protocol.
|
|
38
|
+
*/
|
|
39
|
+
export type MobileNotificationTypeId =
|
|
40
|
+
| 'new_follower'
|
|
41
|
+
| 'followed_you_back'
|
|
42
|
+
| 'order_fill'
|
|
43
|
+
| 'order_update'
|
|
44
|
+
| 'liquidation'
|
|
45
|
+
| 'trader_opened'
|
|
46
|
+
| 'trader_closed'
|
|
47
|
+
| 'position_milestone'
|
|
48
|
+
| 'market_movement'
|
|
49
|
+
| 'trader_streak'
|
|
50
|
+
| 'account_drama'
|
|
51
|
+
| 'weekly_digest';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Named navigation destinations supported by notification payloads.
|
|
55
|
+
*/
|
|
56
|
+
export type MobileNotificationRouteName = MobileServerNotificationRoute['name'];
|
|
57
|
+
|
|
58
|
+
/** Position milestone options supported by notification settings. */
|
|
59
|
+
export const MOBILE_NOTIFICATION_POSITION_THRESHOLDS = [10, 25, 50] as const;
|
|
60
|
+
|
|
61
|
+
/** Minimum trade-size options, in whole USD, supported by notification settings. */
|
|
62
|
+
export const MOBILE_NOTIFICATION_MIN_TRADE_SIZES_USD = [
|
|
63
|
+
0, 100, 1000, 10000,
|
|
64
|
+
] as const;
|
|
65
|
+
|
|
66
|
+
/** Supported notification position milestone threshold. */
|
|
67
|
+
export type MobileNotificationPositionThreshold =
|
|
68
|
+
(typeof MOBILE_NOTIFICATION_POSITION_THRESHOLDS)[number];
|
|
69
|
+
|
|
70
|
+
/** Supported notification minimum trade size, in whole USD. */
|
|
71
|
+
export type MobileNotificationMinTradeSizeUsd =
|
|
72
|
+
(typeof MOBILE_NOTIFICATION_MIN_TRADE_SIZES_USD)[number];
|
|
73
|
+
|
|
74
|
+
/** Supported product analytics interaction. */
|
|
75
|
+
export type MobileProductEventName =
|
|
76
|
+
| 'notif_opened'
|
|
77
|
+
| 'market_viewed'
|
|
78
|
+
| 'chart_opened'
|
|
79
|
+
| 'search';
|
|
80
|
+
|
|
36
81
|
/**
|
|
37
82
|
* Category of push notification.
|
|
38
83
|
*/
|
|
@@ -223,6 +268,127 @@ export interface MobileServerNotificationPreferences {
|
|
|
223
268
|
categories: MobileServerNotificationCategoryPreference[];
|
|
224
269
|
}
|
|
225
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Stored push-device metadata shared by registration requests and token ownership responses.
|
|
273
|
+
*/
|
|
274
|
+
export interface MobileServerNotificationDeviceMetadata {
|
|
275
|
+
/** Device operating system. */
|
|
276
|
+
platform: MobileNotificationPlatform;
|
|
277
|
+
/** Locale tag, or null when absent; at most 35 UTF-8 bytes. */
|
|
278
|
+
locale: string | null;
|
|
279
|
+
/** App version, or null when absent; at most 64 UTF-8 bytes. */
|
|
280
|
+
app_version: string | null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Device capabilities shared by v2 registration and v1/v2 registration queries.
|
|
285
|
+
*/
|
|
286
|
+
export interface MobileServerNotificationCapabilities {
|
|
287
|
+
/** Exactly one supported payload version. */
|
|
288
|
+
payload_versions: [1] | [2];
|
|
289
|
+
/** Supported subset of the registered notification types. */
|
|
290
|
+
type_ids: MobileNotificationTypeId[];
|
|
291
|
+
/** Supported subset of the named destinations. */
|
|
292
|
+
routes: MobileNotificationRouteName[];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Server-side wallet settings; all fields are required on replacement.
|
|
297
|
+
*/
|
|
298
|
+
export interface MobileServerNotificationSettings {
|
|
299
|
+
/** Own position milestones and the initial weekly digest. */
|
|
300
|
+
my_positions: boolean;
|
|
301
|
+
/** One to three unique values; the minimum selects the starting milestone tier. */
|
|
302
|
+
position_thresholds: MobileNotificationPositionThreshold[];
|
|
303
|
+
/** Combined order fill/update switch; does not control liquidation notifications. */
|
|
304
|
+
my_orders: boolean;
|
|
305
|
+
/** Ordinary followed-trader notifications; enabled bells override this switch. */
|
|
306
|
+
following_trades: boolean;
|
|
307
|
+
/** Notifications about posts by followed traders. */
|
|
308
|
+
following_posts: boolean;
|
|
309
|
+
/** Minimum trade size for trade notifications, in whole USD. */
|
|
310
|
+
min_trade_size_usd: MobileNotificationMinTradeSizeUsd;
|
|
311
|
+
/** New followers and follow-backs. */
|
|
312
|
+
new_followers: boolean;
|
|
313
|
+
/** Maps to the legacy announcement preference. */
|
|
314
|
+
announcements: boolean;
|
|
315
|
+
/** Opt-in trader streak and account drama discovery notifications. */
|
|
316
|
+
discovery: boolean;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Server-side allowlisted destination; notification data must never be interpreted as an arbitrary URL.
|
|
321
|
+
*/
|
|
322
|
+
export type MobileServerNotificationRoute =
|
|
323
|
+
| {
|
|
324
|
+
/** Public profile destination. */
|
|
325
|
+
name: 'profile';
|
|
326
|
+
/** Profile subaccount as bytes32. */
|
|
327
|
+
subaccount: Hex;
|
|
328
|
+
}
|
|
329
|
+
| {
|
|
330
|
+
/** Market destination. */
|
|
331
|
+
name: 'market';
|
|
332
|
+
/** Positive product ID. */
|
|
333
|
+
product_id: number;
|
|
334
|
+
}
|
|
335
|
+
| {
|
|
336
|
+
/** Own portfolio destination. */
|
|
337
|
+
name: 'portfolio';
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Server-side v2 push/inbox navigation data, not a complete historical trade or notification body.
|
|
342
|
+
*/
|
|
343
|
+
export interface MobileServerNotificationPayloadV2 {
|
|
344
|
+
/** Payload protocol version. */
|
|
345
|
+
payload_version: 2;
|
|
346
|
+
/** Logical notification UUID, used for click deduplication. */
|
|
347
|
+
notification_id: string;
|
|
348
|
+
/** Intended wallet; private navigation must wait for this wallet to connect. */
|
|
349
|
+
recipient_wallet: Hex;
|
|
350
|
+
/** Registered notification type. */
|
|
351
|
+
type_id: MobileNotificationTypeId;
|
|
352
|
+
/** Supported template version. */
|
|
353
|
+
template_version: 1;
|
|
354
|
+
/** Named destination and its parameters. */
|
|
355
|
+
route: MobileServerNotificationRoute;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Server-side accepted logical notification; does not prove device delivery or read status.
|
|
360
|
+
*/
|
|
361
|
+
export interface MobileServerNotificationInboxItem {
|
|
362
|
+
/** Logical notification UUID. */
|
|
363
|
+
notification_id: string;
|
|
364
|
+
/** Registered notification type. */
|
|
365
|
+
type_id: MobileNotificationTypeId;
|
|
366
|
+
/** Navigation data, without title, body, or read state. */
|
|
367
|
+
data: MobileServerNotificationPayloadV2;
|
|
368
|
+
/** Intent acceptance time in milliseconds. */
|
|
369
|
+
created_at: number;
|
|
370
|
+
/** Backend-computed trade attribution. */
|
|
371
|
+
trade_within_24h: boolean;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Server-side product analytics event; unused nullable fields must be explicitly null.
|
|
376
|
+
*/
|
|
377
|
+
export interface MobileServerProductEvent {
|
|
378
|
+
/** Stable event UUID; reuse across retries. */
|
|
379
|
+
client_event_id: string;
|
|
380
|
+
/** Supported interaction; notif_opened is not a read-state mutation. */
|
|
381
|
+
event_name: MobileProductEventName;
|
|
382
|
+
/** Required for notif_opened and owned by the signing wallet; otherwise null. */
|
|
383
|
+
notification_id: string | null;
|
|
384
|
+
/** Actual event time in milliseconds, within the past 7 days or next 5 minutes. */
|
|
385
|
+
occurred_at: number;
|
|
386
|
+
/** Required for market_viewed/chart_opened; otherwise null. */
|
|
387
|
+
product_id: number | null;
|
|
388
|
+
/** Required for search, nonblank and at most 80 Unicode characters; otherwise null. */
|
|
389
|
+
query: string | null;
|
|
390
|
+
}
|
|
391
|
+
|
|
226
392
|
/** Content filters accepted by the six social feed lists. Empty or omitted means all. */
|
|
227
393
|
export type MobileSocialFeedType =
|
|
228
394
|
| 'open'
|
|
@@ -432,10 +598,39 @@ export interface MobileServerSocialFeedCommentPreview {
|
|
|
432
598
|
|
|
433
599
|
/** Exposure requirements associated with a reading session. */
|
|
434
600
|
export interface MobileServerSocialFeedViewTracking {
|
|
435
|
-
/**
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
/** Uploaded feed image metadata; bytes are fetched separately with the reading session. */
|
|
609
|
+
export interface MobileServerSocialFeedImage {
|
|
610
|
+
/** Image UUID. */
|
|
611
|
+
image_id: string;
|
|
612
|
+
/** Backend path for the original image bytes. */
|
|
613
|
+
url: string;
|
|
614
|
+
/** Display-orientation width in pixels. */
|
|
615
|
+
width: number;
|
|
616
|
+
/** Display-orientation height in pixels. */
|
|
617
|
+
height: number;
|
|
618
|
+
/** Detected content type. */
|
|
619
|
+
content_type: 'image/jpeg' | 'image/png';
|
|
620
|
+
/** Original file size in bytes. */
|
|
621
|
+
byte_size: number;
|
|
622
|
+
/** Always-present thumbnail (longest edge ≤ 512, never upscaled). */
|
|
623
|
+
thumbnail: MobileServerSocialFeedImageThumbnail;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Thumbnail metadata for an uploaded feed image. */
|
|
627
|
+
export interface MobileServerSocialFeedImageThumbnail {
|
|
628
|
+
/** Backend path for the thumbnail bytes. */
|
|
629
|
+
url: string;
|
|
630
|
+
/** Thumbnail width in pixels. */
|
|
631
|
+
width: number;
|
|
632
|
+
/** Thumbnail height in pixels. */
|
|
633
|
+
height: number;
|
|
439
634
|
}
|
|
440
635
|
|
|
441
636
|
/** Coverage of the candidate authors in a qualification snapshot. */
|
|
@@ -484,6 +679,8 @@ export interface MobileServerSocialFeedItemBase {
|
|
|
484
679
|
|
|
485
680
|
/** Content shared by authored posts and trade cards. */
|
|
486
681
|
export interface MobileServerSocialFeedContentBase extends MobileServerSocialFeedItemBase {
|
|
682
|
+
/** Bound images in display order; empty when none. */
|
|
683
|
+
images: MobileServerSocialFeedImage[];
|
|
487
684
|
/** Card text, empty for automatic cards. */
|
|
488
685
|
text: string;
|
|
489
686
|
/** Mentions in the original, unmodified text. */
|
|
@@ -559,13 +756,13 @@ export type MobileServerSocialFeedItem =
|
|
|
559
756
|
| MobileServerSocialFeedPost
|
|
560
757
|
| MobileServerSocialFeedReply;
|
|
561
758
|
|
|
562
|
-
/**
|
|
759
|
+
/** Idle-renewed social feed reading session. Credentials and cursors must be returned unchanged. */
|
|
563
760
|
export interface MobileServerSocialFeedReadSession {
|
|
564
761
|
/** Session identifier. */
|
|
565
762
|
session_id: string;
|
|
566
763
|
/** Feed, item, or full position history session. */
|
|
567
764
|
mode: MobileSocialFeedReadSessionMode;
|
|
568
|
-
/**
|
|
765
|
+
/** Current idle expiration in Unix milliseconds; legitimate session requests renew it. */
|
|
569
766
|
expires_at_ms: number;
|
|
570
767
|
/** Visibility requirements for explicit exposure reporting. */
|
|
571
768
|
view_tracking: MobileServerSocialFeedViewTracking;
|
|
@@ -737,6 +934,8 @@ export interface MobileServerSocialFeedPost extends MobileServerSocialFeedConten
|
|
|
737
934
|
export interface MobileServerSocialFeedReply extends MobileServerSocialFeedItemBase {
|
|
738
935
|
/** Reply discriminant. */
|
|
739
936
|
kind: 'reply';
|
|
937
|
+
/** Bound images in display order; empty when none. */
|
|
938
|
+
images: MobileServerSocialFeedImage[];
|
|
740
939
|
/** Original text. */
|
|
741
940
|
text: string;
|
|
742
941
|
/** Mentions in the text. */
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Hex } from 'viem';
|
|
2
2
|
import { MobileServerSuccessResponse } from './serverBaseTypes';
|
|
3
3
|
import {
|
|
4
|
-
MobileNotificationPlatform,
|
|
5
4
|
MobileServerAvatarPreset,
|
|
6
5
|
MobileServerFeedTrade,
|
|
7
6
|
MobileServerFollowListAccount,
|
|
7
|
+
MobileServerNotificationCapabilities,
|
|
8
|
+
MobileServerNotificationDeviceMetadata,
|
|
9
|
+
MobileServerNotificationInboxItem,
|
|
8
10
|
MobileServerNotificationPreferences,
|
|
11
|
+
MobileServerNotificationSettings,
|
|
9
12
|
MobileServerProfile,
|
|
10
13
|
MobileServerSocialFeedActionCandidate,
|
|
11
14
|
MobileServerSocialFeedActionMarker,
|
|
@@ -35,8 +38,27 @@ import {
|
|
|
35
38
|
import {
|
|
36
39
|
MobileServerPublicQueryRequestByType,
|
|
37
40
|
MobileServerPublicQueryRequestType,
|
|
41
|
+
MobileServerSignedQueryRequestType,
|
|
38
42
|
} from './serverRequestTypes';
|
|
39
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Success payloads for each signed `query`, keyed by request `type`.
|
|
46
|
+
*/
|
|
47
|
+
export interface MobileServerSignedQueryResponseByType {
|
|
48
|
+
/** Wallet subscriptions and the signing Identity's bells. */
|
|
49
|
+
notification_subscriptions: MobileServerNotificationSubscriptionsResponse;
|
|
50
|
+
/** Private notification page. */
|
|
51
|
+
notification_inbox_v2: MobileServerNotificationInboxV2Response;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Full success response for a signed `query`, with its payload inlined alongside `status`.
|
|
56
|
+
*/
|
|
57
|
+
export type MobileServerSignedQuerySuccessResponse<
|
|
58
|
+
T extends MobileServerSignedQueryRequestType =
|
|
59
|
+
MobileServerSignedQueryRequestType,
|
|
60
|
+
> = MobileServerSuccessResponse & MobileServerSignedQueryResponseByType[T];
|
|
61
|
+
|
|
40
62
|
/**
|
|
41
63
|
* Unsigned `public_query` request body: a `type` discriminant flattened with its params.
|
|
42
64
|
*/
|
|
@@ -165,15 +187,58 @@ export interface MobileServerNotificationPreferencesResponse {
|
|
|
165
187
|
preferences: MobileServerNotificationPreferences;
|
|
166
188
|
}
|
|
167
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Payload of the `notification_registration_v2` public query, including legacy registrations.
|
|
192
|
+
*/
|
|
193
|
+
export interface MobileServerNotificationRegistrationV2Response {
|
|
194
|
+
/** Opaque decimal string; do not convert to a JavaScript number. */
|
|
195
|
+
registration_generation: string;
|
|
196
|
+
/** Persisted capabilities of the most recent registration; legacy type/route arrays are empty. */
|
|
197
|
+
capabilities: MobileServerNotificationCapabilities;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Full settings document returned by both `notification_settings_v2` and `update_notification_settings_v2`.
|
|
202
|
+
*/
|
|
203
|
+
export interface MobileServerNotificationSettingsV2Response {
|
|
204
|
+
/** Settings schema version. */
|
|
205
|
+
schema_version: 2;
|
|
206
|
+
/** Settings revision, independent of the subscription revision. */
|
|
207
|
+
revision: number;
|
|
208
|
+
/** Full saved settings. */
|
|
209
|
+
settings: MobileServerNotificationSettings;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Payload of the `notification_subscriptions` signed query.
|
|
214
|
+
*/
|
|
215
|
+
export interface MobileServerNotificationSubscriptionsResponse {
|
|
216
|
+
/** Wallet-level subscription revision; read again after every mutation. */
|
|
217
|
+
revision: number;
|
|
218
|
+
/** Whether the one-time local favorites import has completed. */
|
|
219
|
+
favorites_imported: boolean;
|
|
220
|
+
/** Subscribed market IDs in ascending order. */
|
|
221
|
+
market_ids: number[];
|
|
222
|
+
/** Followed subaccounts with bells enabled for the signing Identity. */
|
|
223
|
+
trader_bells: Hex[];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Payload of the `notification_inbox_v2` signed query, ordered newest first.
|
|
228
|
+
*/
|
|
229
|
+
export interface MobileServerNotificationInboxV2Response {
|
|
230
|
+
/** Accepted intents owned by the signing wallet. */
|
|
231
|
+
notifications: MobileServerNotificationInboxItem[];
|
|
232
|
+
/** Pass unchanged to the next request, or null when pagination is complete. */
|
|
233
|
+
next_cursor: string | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
168
236
|
/**
|
|
169
237
|
* Payload of the `registered_wallet` public query success response. Inlined into the envelope rather than
|
|
170
238
|
* nested under a key, matching the backend's `RegisteredWalletResponse`.
|
|
171
239
|
*/
|
|
172
|
-
export interface MobileServerRegisteredWalletResponse {
|
|
240
|
+
export interface MobileServerRegisteredWalletResponse extends MobileServerNotificationDeviceMetadata {
|
|
173
241
|
wallet: Hex;
|
|
174
|
-
platform: MobileNotificationPlatform;
|
|
175
|
-
locale: string | null;
|
|
176
|
-
app_version: string | null;
|
|
177
242
|
token_fingerprint_prefix: string;
|
|
178
243
|
last_seen_at: number;
|
|
179
244
|
}
|
|
@@ -185,6 +250,10 @@ export interface MobileServerRegisteredWalletResponse {
|
|
|
185
250
|
* by {@link MobileServerPublicQuerySuccessResponse}.
|
|
186
251
|
*/
|
|
187
252
|
export interface MobileServerPublicQueryResponseByType {
|
|
253
|
+
/** Latest saved device generation and v1/v2 capabilities. */
|
|
254
|
+
notification_registration_v2: MobileServerNotificationRegistrationV2Response;
|
|
255
|
+
/** Full settings document with its revision. */
|
|
256
|
+
notification_settings_v2: MobileServerNotificationSettingsV2Response;
|
|
188
257
|
/** Open position candidates. */
|
|
189
258
|
self_feed_positions: MobileServerSocialFeedPositionsResponse;
|
|
190
259
|
/** Position command resolution. */
|
|
@@ -1,13 +1,73 @@
|
|
|
1
1
|
import { Hex } from 'viem';
|
|
2
2
|
import { MobileSignedInner } from '../signing/types';
|
|
3
3
|
import {
|
|
4
|
+
MobileNotificationRouteName,
|
|
5
|
+
MobileNotificationTypeId,
|
|
6
|
+
MobileServerNotificationDeviceMetadata,
|
|
4
7
|
MobileServerNotificationPreferences,
|
|
8
|
+
MobileServerNotificationSettings,
|
|
5
9
|
MobileServerSocialFeedChartRange,
|
|
6
10
|
MobileSocialFeedChartInterval,
|
|
7
11
|
MobileSocialFeedMarketCategory,
|
|
8
12
|
MobileSocialFeedPresenceState,
|
|
9
13
|
MobileSocialFeedType,
|
|
10
14
|
} from './serverModelTypes';
|
|
15
|
+
import type { MobileServerSignedQueryResponseByType } from './serverQueryTypes';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Common wire-format credential for token-authenticated notification requests.
|
|
19
|
+
* Unregistration also accepts an inactive or unknown well-formed token.
|
|
20
|
+
*/
|
|
21
|
+
export interface MobileServerWithExpoTokenParams {
|
|
22
|
+
/** Expo push token; never expose it to another user. */
|
|
23
|
+
expo_token: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registration fields shared by v1 and v2 signed inner payloads.
|
|
28
|
+
* Nullable metadata keys remain present when signing so the MessagePack hash matches the backend.
|
|
29
|
+
*/
|
|
30
|
+
export interface MobileServerRegisterExpoTokenParams
|
|
31
|
+
extends
|
|
32
|
+
MobileServerWithExpoTokenParams,
|
|
33
|
+
MobileServerNotificationDeviceMetadata {}
|
|
34
|
+
|
|
35
|
+
/** Registration request with a v2-only capability declaration. */
|
|
36
|
+
export interface MobileServerRegisterExpoTokenV2Params extends MobileServerRegisterExpoTokenParams {
|
|
37
|
+
/** Actual notification features supported by the device. */
|
|
38
|
+
capabilities: {
|
|
39
|
+
/** Registration accepts only the v2 protocol. */
|
|
40
|
+
payload_versions: [2];
|
|
41
|
+
/** Supported notification types. */
|
|
42
|
+
type_ids: MobileNotificationTypeId[];
|
|
43
|
+
/** Supported navigation destinations. */
|
|
44
|
+
routes: MobileNotificationRouteName[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One subscription operation; exactly one operation is non-null and unused operations must be null.
|
|
50
|
+
*/
|
|
51
|
+
export interface MobileServerNotificationSubscriptionsUpdate {
|
|
52
|
+
/** Latest subscription revision, not the settings revision. */
|
|
53
|
+
expected_revision: number;
|
|
54
|
+
/** Set one bell; enabling requires an existing follow relationship. */
|
|
55
|
+
trader_bell: {
|
|
56
|
+
/** Target non-isolated subaccount belonging to another wallet. */
|
|
57
|
+
subaccount: Hex;
|
|
58
|
+
/** Desired bell state. */
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
} | null;
|
|
61
|
+
/** Subscribe or unsubscribe one known market. */
|
|
62
|
+
market: {
|
|
63
|
+
/** Positive market product ID. */
|
|
64
|
+
product_id: number;
|
|
65
|
+
/** Desired subscription state. */
|
|
66
|
+
subscribed: boolean;
|
|
67
|
+
} | null;
|
|
68
|
+
/** One-time merge of up to 500 local market IDs; mutually exclusive with the other operations. */
|
|
69
|
+
import_markets: number[] | null;
|
|
70
|
+
}
|
|
11
71
|
|
|
12
72
|
/**
|
|
13
73
|
* Opt-in extras for a `profiles` query. Each one costs extra rate-limit weight per profile and adds fields
|
|
@@ -68,7 +128,7 @@ export interface MobileServerSocialFeedListParams {
|
|
|
68
128
|
minimum_notional?: number;
|
|
69
129
|
/** Page size, 1–50; defaults to 20. */
|
|
70
130
|
limit?: number;
|
|
71
|
-
/**
|
|
131
|
+
/** Original reading session; required with cursor. Legitimate requests renew its idle expiration. */
|
|
72
132
|
session_id?: string;
|
|
73
133
|
/** Opaque pagination cursor, distinct from update_cursor and watermark. */
|
|
74
134
|
cursor?: string;
|
|
@@ -213,6 +273,8 @@ export interface MobileServerPublicQueryRequestByType {
|
|
|
213
273
|
feed_item: MobileServerSocialFeedReadParams & {
|
|
214
274
|
/** Target item UUID. */
|
|
215
275
|
item_id: string;
|
|
276
|
+
/** Detail-visit event; omit for prefetch. Does not increment the count alone. */
|
|
277
|
+
view_event_id?: string;
|
|
216
278
|
/** Preview size, 0-3. */
|
|
217
279
|
comment_preview_limit?: number;
|
|
218
280
|
/** Omit to create a new reading session. */
|
|
@@ -263,8 +325,12 @@ export interface MobileServerPublicQueryRequestByType {
|
|
|
263
325
|
*/
|
|
264
326
|
cursor?: string;
|
|
265
327
|
};
|
|
266
|
-
notification_preferences:
|
|
267
|
-
|
|
328
|
+
notification_preferences: MobileServerWithExpoTokenParams;
|
|
329
|
+
/** Persisted device generation and capabilities. */
|
|
330
|
+
notification_registration_v2: MobileServerWithExpoTokenParams;
|
|
331
|
+
/** Versioned notification settings. */
|
|
332
|
+
notification_settings_v2: MobileServerWithExpoTokenParams;
|
|
333
|
+
registered_wallet: MobileServerWithExpoTokenParams;
|
|
268
334
|
}
|
|
269
335
|
|
|
270
336
|
/**
|
|
@@ -282,14 +348,24 @@ export type MobileServerPublicQueryRequestType =
|
|
|
282
348
|
* notification writes use the backend's last-write-wins commit order, so callers must serialize them.
|
|
283
349
|
*/
|
|
284
350
|
export interface MobileServerPublicExecuteRequestByType {
|
|
285
|
-
/**
|
|
351
|
+
/** Full settings replacement with optimistic concurrency control. */
|
|
352
|
+
update_notification_settings_v2: MobileServerWithExpoTokenParams & {
|
|
353
|
+
/** Latest settings revision. */
|
|
354
|
+
expected_revision: number;
|
|
355
|
+
/** Complete settings document. */
|
|
356
|
+
settings: MobileServerNotificationSettings;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/** Explicit detail-visit report using the original browsing credential. */
|
|
286
360
|
record_feed_views: {
|
|
287
361
|
/** Original reading session. */
|
|
288
362
|
session_id: string;
|
|
289
|
-
/** Fully delivered interactive items. */
|
|
290
|
-
item_ids: string[];
|
|
291
363
|
/** Original credential. */
|
|
292
364
|
viewer_token: string;
|
|
365
|
+
/** Detail-visit event previously registered on feed_item. */
|
|
366
|
+
view_event_id: string;
|
|
367
|
+
/** Exactly one fully delivered detail-target item. */
|
|
368
|
+
item_ids: string[];
|
|
293
369
|
};
|
|
294
370
|
/** Per-page visitor lease; no wallet signature. */
|
|
295
371
|
position_presence: {
|
|
@@ -320,20 +396,33 @@ export type MobileServerPublicExecuteRequestType =
|
|
|
320
396
|
keyof MobileServerPublicExecuteRequestByType;
|
|
321
397
|
|
|
322
398
|
/**
|
|
323
|
-
* Discriminant `type` values for signed `execute` requests
|
|
324
|
-
*
|
|
325
|
-
* params map — a signed request body *is* its inner payload flattened with the envelope, so
|
|
399
|
+
* Discriminant `type` values for signed `execute` requests, excluding signed queries. Unlike the public
|
|
400
|
+
* queries these carry no separate params map — a signed request body is its inner payload flattened with the envelope, so
|
|
326
401
|
* {@link MobileSignedInner} is the single source of truth for both the fields and their significant order.
|
|
327
402
|
*/
|
|
328
|
-
export type MobileServerExecuteRequestType =
|
|
403
|
+
export type MobileServerExecuteRequestType = Exclude<
|
|
404
|
+
MobileSignedInner['type'],
|
|
405
|
+
MobileServerSignedQueryRequestType
|
|
406
|
+
>;
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Discriminant `type` values for signed `query` requests, distinct from unsigned `public_query` requests.
|
|
410
|
+
*/
|
|
411
|
+
export type MobileServerSignedQueryRequestType =
|
|
412
|
+
keyof MobileServerSignedQueryResponseByType;
|
|
329
413
|
|
|
330
414
|
/**
|
|
331
415
|
* Wire `request_type` the backend echoes on failure envelopes, prefixed by the route that produced it:
|
|
332
416
|
* `public_query_*` for unsigned queries, `public_execute_*` for unsigned writes, and `execute_*` for signed
|
|
333
|
-
* writes (e.g. `execute_set_username`)
|
|
334
|
-
*
|
|
417
|
+
* writes (e.g. `execute_set_username`), or `query_*` for signed reads. Unlike the EIP-712 method,
|
|
418
|
+
* these values never carry the `mobile:` prefix. Protected image reads use `feed_image` or
|
|
419
|
+
* `feed_comment_image` without a route prefix; malformed multipart uploads use `execute_image_upload`.
|
|
335
420
|
*/
|
|
336
421
|
export type MobileServerRequestType =
|
|
422
|
+
| 'feed_image'
|
|
423
|
+
| 'feed_comment_image'
|
|
424
|
+
| 'execute_image_upload'
|
|
425
|
+
| `query_${MobileServerSignedQueryRequestType}`
|
|
337
426
|
| `public_query_${MobileServerPublicQueryRequestType}`
|
|
338
427
|
| `public_execute_${MobileServerPublicExecuteRequestType}`
|
|
339
428
|
| `execute_${MobileServerExecuteRequestType}`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import { buildMobileFeedImageMultipart } from './buildMobileFeedImageMultipart';
|
|
3
|
+
|
|
4
|
+
describe('buildMobileFeedImageMultipart', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
jest.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('keeps multipart overhead fixed across timestamp and random boundary lengths', () => {
|
|
10
|
+
const payload = '{"type":"create_feed_post_image_uploads"}';
|
|
11
|
+
const fileBytes = new Uint8Array([0, 1, 255]);
|
|
12
|
+
const expected = buildMobileFeedImageMultipart(payload, fileBytes);
|
|
13
|
+
for (const timestamp of [0, 1_700_000_000_000, 8_640_000_000_000_000]) {
|
|
14
|
+
for (const random of [0, 0.000001, 0.5, 0.999999]) {
|
|
15
|
+
jest.spyOn(Date, 'now').mockReturnValue(timestamp);
|
|
16
|
+
jest.spyOn(Math, 'random').mockReturnValue(random);
|
|
17
|
+
const actual = buildMobileFeedImageMultipart(payload, fileBytes);
|
|
18
|
+
expect(actual.body.byteLength).toBe(expected.body.byteLength);
|
|
19
|
+
expect(actual.contentType.length).toBe(expected.contentType.length);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('emits payload and file parts with a boundary Content-Type', () => {
|
|
25
|
+
const fileBytes = new Uint8Array([0, 1, 127, 128, 255, 13, 10]);
|
|
26
|
+
const { body, contentType } = buildMobileFeedImageMultipart(
|
|
27
|
+
'{"type":"create_feed_post_image_uploads"}',
|
|
28
|
+
fileBytes,
|
|
29
|
+
);
|
|
30
|
+
const text = String.fromCharCode(...body);
|
|
31
|
+
expect(contentType.startsWith('multipart/form-data; boundary=')).toBe(true);
|
|
32
|
+
expect(text).toContain('name="payload"');
|
|
33
|
+
expect(text).toContain('name="file"; filename="image"');
|
|
34
|
+
expect(text).toContain('{"type":"create_feed_post_image_uploads"}');
|
|
35
|
+
const boundary = contentType.split('boundary=')[1];
|
|
36
|
+
const fileStart = text.indexOf('\r\n\r\n', text.indexOf('name="file"')) + 4;
|
|
37
|
+
expect(body.slice(fileStart, fileStart + fileBytes.length)).toEqual(
|
|
38
|
+
fileBytes,
|
|
39
|
+
);
|
|
40
|
+
expect(text.slice(fileStart + fileBytes.length)).toBe(
|
|
41
|
+
`\r\n--${boundary}--\r\n`,
|
|
42
|
+
);
|
|
43
|
+
expect(text.startsWith(`--${boundary}\r\n`)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { concatBytes, stringToBytes } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds a multipart/form-data body for feed image uploads without relying on DOM FormData/Blob.
|
|
5
|
+
* Parts are exactly `payload` (JSON text) and `file` (raw bytes), matching the mobile execute contract.
|
|
6
|
+
* @param payloadJson - Stringified signed execute body.
|
|
7
|
+
* @param fileBytes - Original image bytes.
|
|
8
|
+
* @returns Raw multipart body and its Content-Type header value including boundary.
|
|
9
|
+
*/
|
|
10
|
+
export function buildMobileFeedImageMultipart(
|
|
11
|
+
payloadJson: string,
|
|
12
|
+
fileBytes: Uint8Array,
|
|
13
|
+
): { body: Uint8Array; contentType: string } {
|
|
14
|
+
// Fixed-length boundaries keep multipart overhead identical for the pre-signing size check and upload.
|
|
15
|
+
const boundary = `----nadoFeedImage${Date.now().toString(16).padStart(14, '0')}${Math.floor(
|
|
16
|
+
Math.random() * 1e6,
|
|
17
|
+
)
|
|
18
|
+
.toString(16)
|
|
19
|
+
.padStart(5, '0')}`;
|
|
20
|
+
const preamble =
|
|
21
|
+
`--${boundary}\r\n` +
|
|
22
|
+
'Content-Disposition: form-data; name="payload"\r\n' +
|
|
23
|
+
'Content-Type: application/json\r\n\r\n' +
|
|
24
|
+
`${payloadJson}\r\n` +
|
|
25
|
+
`--${boundary}\r\n` +
|
|
26
|
+
'Content-Disposition: form-data; name="file"; filename="image"\r\n' +
|
|
27
|
+
'Content-Type: application/octet-stream\r\n\r\n';
|
|
28
|
+
return {
|
|
29
|
+
body: concatBytes([
|
|
30
|
+
stringToBytes(preamble),
|
|
31
|
+
fileBytes,
|
|
32
|
+
stringToBytes(`\r\n--${boundary}--\r\n`),
|
|
33
|
+
]),
|
|
34
|
+
contentType: `multipart/form-data; boundary=${boundary}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/src/utils/feed.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export const MOBILE_FEED_MAX_PAGE_SIZE = 50;
|
|
6
6
|
|
|
7
|
+
/** Maximum feed image upload request size, including the signed payload and multipart framing (2 MiB). */
|
|
8
|
+
export const MOBILE_FEED_IMAGE_MAX_REQUEST_BYTES = 2 * 1024 * 1024;
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Lowest minimum notional the backend accepts, in whole dollars. Filtered requests below this fail with
|
|
9
12
|
* `INVALID_FEED_FILTER`; omitting the filter entirely returns the unfiltered feed.
|