@cloudparse/up-miniapps-sdk 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -70,6 +70,8 @@ interface IUserProfile {
70
70
  countryCode: string;
71
71
  /** Phone number without country code. */
72
72
  localNumber: string;
73
+ /** How the user's account was created/registered. */
74
+ registrationType: "up" | "apple" | "google";
73
75
  /** Admin status. */
74
76
  admin: boolean;
75
77
  /** UP points balance. */
@@ -1608,4 +1610,163 @@ interface ApiServiceClient {
1608
1610
  */
1609
1611
  declare function createApiService(options: CreateApiServiceOptions): ApiServiceClient;
1610
1612
 
1611
- export { type ApiServiceClient, type BridgeMessageType, BridgeMocker, type BridgeMockerProps, type CenterCallbackParams, type CreateApiServiceOptions, type CustomControl, DebugConsole, type DebugConsoleProps, type DeclarativeJourneyOptions, type DrawerState, type EtaData, type FidelityTransactionTypeType, type FlyToParams, GeolocationEvent, GlobalEvent, type HapticStyleType, type IBridgeEnvelope, type IContextState, type IEdgeInsets, type IFidelityBalance, type IFidelityHistoryParams, type IFidelityTransaction, type IGenerateQrCodeOptions, type IInitializationOptions, type IInitializationResponse, type ILocationData, type INetworkOptions, type INetworkResponse, type IPaymentRequest, type IPickImageResult, type ISaveImageResult, type IUserProfile, type LiveRoutingOptions, type LiveRoutingResult, type LocationStreamConfig, Map, MapControls, MapControlsContext, type MapControlsProps, type MapControlsState, type MapInsets, MapMenu, MapMenuDivider, type MapMenuDividerProps, MapMenuFooter, type MapMenuFooterProps, MapMenuItem, type MapMenuItemProps, type MapMenuProps, MapMenuTitle, type MapMenuTitleProps, type MapProps, type MenuItem, MiniAppSDK, MockLocationContext, type MockLocationContextType, type NetworkMethodType, type POI, QRScanner, type QRScannerProps, RouteLayer, type RouteLayerProps, RouteRequest, RouteResponse, RouteStep, type SDKEvent, SDKMapProvider, SDKProvider, type SDKProviderProps, type ScrollConfig, type StreamAccelerometerData, type StreamHeadingData, type StreamLocationData, type StreamUpdatePayload, type SwipeAxisPriority, SwipeCarousel, type SwipeCarouselItem, type SwipeCarouselProps, type SwipeCarouselVerticalDragConfig, UIEvent, type UseJourneyOptions, type UseJourneyResult, type UserLocationState, createApiService, sdk as default, renderMap, sdk, useIsMocked, useJourney, useLiveRouting, useMapControls, useMockLocation, useRoute, useTrimmedRoute, useUserLocation };
1613
+ interface WsMessage {
1614
+ event?: string;
1615
+ [key: string]: unknown;
1616
+ }
1617
+ interface BaseWsServiceOptions<TMessage extends WsMessage> {
1618
+ /**
1619
+ * The REST client from `createApiService` — reused here for two things:
1620
+ * its live `wsUrl` (so this shares the exact same URL resolution as
1621
+ * every REST/GraphQL call, no separate copy to go stale), and its
1622
+ * `post()` to mint a short-lived WS auth ticket in production (see
1623
+ * `ticketPath` below).
1624
+ */
1625
+ api: Pick<ApiServiceClient, "post" | "wsUrl">;
1626
+ /**
1627
+ * REST path (relative, via `api.post`) that mints a short-lived,
1628
+ * single-use ticket for the WS handshake — a mini-app's JS never holds
1629
+ * its own real bearer token in production (`sdk.network.request` proxies
1630
+ * every REST/GraphQL call through the native host shell, which injects
1631
+ * auth on its own side; a raw browser `WebSocket` has no equivalent
1632
+ * bridge and no way to set a custom header, so it authenticates via a
1633
+ * ticket fetched over that same working REST path instead). Expected to
1634
+ * return `{ ticket: string }`. Defaults to `"/ws-ticket"`, matching the
1635
+ * established backend convention (see up-miniapps-backend's
1636
+ * `GetWSTicket`). Pass `null` to disable ticket auth entirely — only
1637
+ * appropriate for a mini-app whose backend has no ws-ticket route yet,
1638
+ * or a genuinely public route (see `requireAuth` below).
1639
+ */
1640
+ ticketPath?: string | null;
1641
+ /**
1642
+ * Raw JWT to use when NOT running inside the native shell (standalone
1643
+ * dev browser) — the consuming app reads its own build-time
1644
+ * `import.meta.env.VITE_DEV_TOKEN` and passes it here; the SDK itself
1645
+ * has no access to a consuming app's Vite env (this package is bundled
1646
+ * by tsup, not Vite). Only ever used outside the shell — a ticket is
1647
+ * always preferred when `isRunningInsideNativeShell()` is true.
1648
+ */
1649
+ devToken?: string | null;
1650
+ /**
1651
+ * Overrides the resolved WS URL outright, for a mini-app whose WS
1652
+ * gateway genuinely lives somewhere `api.wsUrl` doesn't cover. Most
1653
+ * consumers should leave this unset and rely on `api.wsUrl`.
1654
+ */
1655
+ baseUrl?: string;
1656
+ /**
1657
+ * Extra static query params appended to every connection URL. Browsers
1658
+ * (and this WebView) give a raw `WebSocket` no custom-header API — a
1659
+ * query param is the closest equivalent, e.g. for a custom auth scheme
1660
+ * on top of the ticket/token this already sends.
1661
+ */
1662
+ query?: Record<string, string>;
1663
+ /**
1664
+ * When unset/`true` (the default), `connect()` refuses — warns and
1665
+ * delays rather than opening an unauthenticated socket — if neither a
1666
+ * ticket nor `devToken` is available. This is a deliberate safety net:
1667
+ * a channel-scoped app (ticket-authorized per-room access) accidentally
1668
+ * shipping with no auth configured would silently become an open room,
1669
+ * not a loud failure.
1670
+ *
1671
+ * Set to `false` only for a backend route that's genuinely public with
1672
+ * no per-connection identity at all — e.g. up-miniapps-by-night's `/ws`,
1673
+ * which has no auth middleware by design, broadcasts the same public
1674
+ * data to every connection, and has no rooms to gate. `connect()` then
1675
+ * opens the socket with no ticket/token query param whatsoever. Pair
1676
+ * with `ticketPath: null` (no point minting a ticket nobody checks).
1677
+ */
1678
+ requireAuth?: boolean;
1679
+ }
1680
+ /** Options for the channel-scoped mode — pass `channelField` to subscribe/unsubscribe to specific rooms. */
1681
+ interface CreateChannelWsServiceOptions<TMessage extends WsMessage = WsMessage> extends BaseWsServiceOptions<TMessage> {
1682
+ /**
1683
+ * Name of the field on each message that identifies which "room"/channel
1684
+ * it belongs to (e.g. `"groupUuid"`, `"gameUuid"`). Also the field name
1685
+ * used in the `{action, [channelField]: id}` subscribe/unsubscribe wire
1686
+ * messages sent to the backend. Presence of this field is what selects
1687
+ * channel mode over broadcast mode — see `CreateBroadcastWsServiceOptions`.
1688
+ */
1689
+ channelField: string;
1690
+ }
1691
+ /** Options for broadcast mode — omit `channelField` for a single global socket with no room concept. */
1692
+ interface CreateBroadcastWsServiceOptions<TMessage extends WsMessage = WsMessage> extends BaseWsServiceOptions<TMessage> {
1693
+ channelField?: undefined;
1694
+ }
1695
+ type CreateWsServiceOptions<TMessage extends WsMessage = WsMessage> = CreateChannelWsServiceOptions<TMessage> | CreateBroadcastWsServiceOptions<TMessage>;
1696
+ interface WsServiceClient<TMessage extends WsMessage = WsMessage> {
1697
+ /** Opens the connection if not already open/connecting. Safe to call repeatedly. */
1698
+ connect(): Promise<void>;
1699
+ /** Closes the connection and cancels any pending reconnect — no further automatic reconnect happens until `connect()`/`subscribe()` is called again. */
1700
+ disconnect(): void;
1701
+ /**
1702
+ * Subscribes to every message for one channel id. Sends the shared
1703
+ * `{action: "subscribe", [channelField]: id}` wire message automatically
1704
+ * on the first subscriber for that channel (and `"unsubscribe"` on the
1705
+ * last one leaving), and implicitly calls `connect()`. Returns an
1706
+ * unsubscribe function. Multiple concurrent channel ids are fully
1707
+ * supported — each gets its own wire subscribe/unsubscribe message and
1708
+ * its own dispatch, independent of every other channel's subscribers.
1709
+ */
1710
+ subscribe(channelId: string, callback: (message: TMessage) => void): () => void;
1711
+ /**
1712
+ * Subscribes to every message this socket ever receives, regardless of
1713
+ * channel — no wire message is sent for this (there's nothing to
1714
+ * subscribe/unsubscribe to, it's just an additional tap on the same
1715
+ * stream). Runs *alongside* any active `subscribe()` channel
1716
+ * subscriptions, socket.io-style ("join specific rooms AND listen
1717
+ * globally at once") — a message matching an active channel id is
1718
+ * delivered to both that channel's subscribers and every `onMessage`
1719
+ * listener. Implicitly calls `connect()`. Returns an unsubscribe
1720
+ * function.
1721
+ */
1722
+ onMessage(callback: (message: TMessage) => void): () => void;
1723
+ }
1724
+ interface BroadcastWsServiceClient<TMessage extends WsMessage = WsMessage> {
1725
+ /** Opens the connection if not already open/connecting. Safe to call repeatedly. */
1726
+ connect(): Promise<void>;
1727
+ /** Closes the connection and cancels any pending reconnect. */
1728
+ disconnect(): void;
1729
+ /**
1730
+ * Subscribes to every message this socket ever receives — there's no
1731
+ * room/channel concept in broadcast mode, so no subscribe/unsubscribe
1732
+ * wire message is ever sent; the backend just pushes to every connected
1733
+ * client. Implicitly calls `connect()`. Returns an unsubscribe function.
1734
+ */
1735
+ onMessage(callback: (message: TMessage) => void): () => void;
1736
+ }
1737
+ /**
1738
+ * Creates a reconnecting, (usually) authenticated WebSocket client — the WS
1739
+ * counterpart to `createApiService`. Centralizes exactly what every
1740
+ * `up-miniapps-*` app was independently hand-rolling with small drifting
1741
+ * bugs (one app's WS URL was captured once at construction and never
1742
+ * recomputed for the whole session; another always used a dev-only token
1743
+ * even in production, so it could never actually authenticate on a real
1744
+ * device): live URL resolution, ticket-based production auth with a
1745
+ * standalone-dev-token fallback, and exponential-backoff reconnect.
1746
+ *
1747
+ * Two modes, selected by whether `channelField` is present:
1748
+ * - **Channel mode** (`channelField` set): room-scoped pub/sub, matches
1749
+ * up-miniapps-split/up-miniapps-cursor's group/game-room shape. Returns a
1750
+ * `WsServiceClient` with `subscribe(channelId, cb)` — multiple concurrent
1751
+ * channel ids are fully supported — PLUS `onMessage(cb)`, a catch-all that
1752
+ * runs alongside any active channel subscriptions (socket.io-style: join
1753
+ * specific rooms and listen globally at the same time, on one socket).
1754
+ * - **Broadcast mode** (`channelField` omitted): one global socket, no
1755
+ * rooms, no subscribe/unsubscribe wire protocol — every message goes to
1756
+ * every listener. Matches up-miniapps-by-night's public live-update feed
1757
+ * (reactions/flash/favorite counts), which has no per-room access to gate
1758
+ * in the first place. Returns a `BroadcastWsServiceClient` with only
1759
+ * `onMessage(cb)` (no `subscribe` — there's no channel id to subscribe to).
1760
+ *
1761
+ * Deliberately stays a thin transport in both modes, same boundary as
1762
+ * `createApiService`: message *content* interpretation (e.g. treating one
1763
+ * event name as carrying real payload while every other message is a bare
1764
+ * cache-invalidation ping) belongs in each app's own thin wrapper around
1765
+ * this, not here — apps differ enough in what they consider safe to expose
1766
+ * over WS that baking that policy into the SDK would be the wrong
1767
+ * boundary.
1768
+ */
1769
+ declare function createWsService<TMessage extends WsMessage = WsMessage>(options: CreateChannelWsServiceOptions<TMessage>): WsServiceClient<TMessage>;
1770
+ declare function createWsService<TMessage extends WsMessage = WsMessage>(options: CreateBroadcastWsServiceOptions<TMessage>): BroadcastWsServiceClient<TMessage>;
1771
+
1772
+ export { type ApiServiceClient, type BridgeMessageType, BridgeMocker, type BridgeMockerProps, type BroadcastWsServiceClient, type CenterCallbackParams, type CreateApiServiceOptions, type CreateBroadcastWsServiceOptions, type CreateChannelWsServiceOptions, type CreateWsServiceOptions, type CustomControl, DebugConsole, type DebugConsoleProps, type DeclarativeJourneyOptions, type DrawerState, type EtaData, type FidelityTransactionTypeType, type FlyToParams, GeolocationEvent, GlobalEvent, type HapticStyleType, type IBridgeEnvelope, type IContextState, type IEdgeInsets, type IFidelityBalance, type IFidelityHistoryParams, type IFidelityTransaction, type IGenerateQrCodeOptions, type IInitializationOptions, type IInitializationResponse, type ILocationData, type INetworkOptions, type INetworkResponse, type IPaymentRequest, type IPickImageResult, type ISaveImageResult, type IUserProfile, type LiveRoutingOptions, type LiveRoutingResult, type LocationStreamConfig, Map, MapControls, MapControlsContext, type MapControlsProps, type MapControlsState, type MapInsets, MapMenu, MapMenuDivider, type MapMenuDividerProps, MapMenuFooter, type MapMenuFooterProps, MapMenuItem, type MapMenuItemProps, type MapMenuProps, MapMenuTitle, type MapMenuTitleProps, type MapProps, type MenuItem, MiniAppSDK, MockLocationContext, type MockLocationContextType, type NetworkMethodType, type POI, QRScanner, type QRScannerProps, RouteLayer, type RouteLayerProps, RouteRequest, RouteResponse, RouteStep, type SDKEvent, SDKMapProvider, SDKProvider, type SDKProviderProps, type ScrollConfig, type StreamAccelerometerData, type StreamHeadingData, type StreamLocationData, type StreamUpdatePayload, type SwipeAxisPriority, SwipeCarousel, type SwipeCarouselItem, type SwipeCarouselProps, type SwipeCarouselVerticalDragConfig, UIEvent, type UseJourneyOptions, type UseJourneyResult, type UserLocationState, type WsMessage, type WsServiceClient, createApiService, createWsService, sdk as default, renderMap, sdk, useIsMocked, useJourney, useLiveRouting, useMapControls, useMockLocation, useRoute, useTrimmedRoute, useUserLocation };
package/dist/index.d.ts CHANGED
@@ -70,6 +70,8 @@ interface IUserProfile {
70
70
  countryCode: string;
71
71
  /** Phone number without country code. */
72
72
  localNumber: string;
73
+ /** How the user's account was created/registered. */
74
+ registrationType: "up" | "apple" | "google";
73
75
  /** Admin status. */
74
76
  admin: boolean;
75
77
  /** UP points balance. */
@@ -1608,4 +1610,163 @@ interface ApiServiceClient {
1608
1610
  */
1609
1611
  declare function createApiService(options: CreateApiServiceOptions): ApiServiceClient;
1610
1612
 
1611
- export { type ApiServiceClient, type BridgeMessageType, BridgeMocker, type BridgeMockerProps, type CenterCallbackParams, type CreateApiServiceOptions, type CustomControl, DebugConsole, type DebugConsoleProps, type DeclarativeJourneyOptions, type DrawerState, type EtaData, type FidelityTransactionTypeType, type FlyToParams, GeolocationEvent, GlobalEvent, type HapticStyleType, type IBridgeEnvelope, type IContextState, type IEdgeInsets, type IFidelityBalance, type IFidelityHistoryParams, type IFidelityTransaction, type IGenerateQrCodeOptions, type IInitializationOptions, type IInitializationResponse, type ILocationData, type INetworkOptions, type INetworkResponse, type IPaymentRequest, type IPickImageResult, type ISaveImageResult, type IUserProfile, type LiveRoutingOptions, type LiveRoutingResult, type LocationStreamConfig, Map, MapControls, MapControlsContext, type MapControlsProps, type MapControlsState, type MapInsets, MapMenu, MapMenuDivider, type MapMenuDividerProps, MapMenuFooter, type MapMenuFooterProps, MapMenuItem, type MapMenuItemProps, type MapMenuProps, MapMenuTitle, type MapMenuTitleProps, type MapProps, type MenuItem, MiniAppSDK, MockLocationContext, type MockLocationContextType, type NetworkMethodType, type POI, QRScanner, type QRScannerProps, RouteLayer, type RouteLayerProps, RouteRequest, RouteResponse, RouteStep, type SDKEvent, SDKMapProvider, SDKProvider, type SDKProviderProps, type ScrollConfig, type StreamAccelerometerData, type StreamHeadingData, type StreamLocationData, type StreamUpdatePayload, type SwipeAxisPriority, SwipeCarousel, type SwipeCarouselItem, type SwipeCarouselProps, type SwipeCarouselVerticalDragConfig, UIEvent, type UseJourneyOptions, type UseJourneyResult, type UserLocationState, createApiService, sdk as default, renderMap, sdk, useIsMocked, useJourney, useLiveRouting, useMapControls, useMockLocation, useRoute, useTrimmedRoute, useUserLocation };
1613
+ interface WsMessage {
1614
+ event?: string;
1615
+ [key: string]: unknown;
1616
+ }
1617
+ interface BaseWsServiceOptions<TMessage extends WsMessage> {
1618
+ /**
1619
+ * The REST client from `createApiService` — reused here for two things:
1620
+ * its live `wsUrl` (so this shares the exact same URL resolution as
1621
+ * every REST/GraphQL call, no separate copy to go stale), and its
1622
+ * `post()` to mint a short-lived WS auth ticket in production (see
1623
+ * `ticketPath` below).
1624
+ */
1625
+ api: Pick<ApiServiceClient, "post" | "wsUrl">;
1626
+ /**
1627
+ * REST path (relative, via `api.post`) that mints a short-lived,
1628
+ * single-use ticket for the WS handshake — a mini-app's JS never holds
1629
+ * its own real bearer token in production (`sdk.network.request` proxies
1630
+ * every REST/GraphQL call through the native host shell, which injects
1631
+ * auth on its own side; a raw browser `WebSocket` has no equivalent
1632
+ * bridge and no way to set a custom header, so it authenticates via a
1633
+ * ticket fetched over that same working REST path instead). Expected to
1634
+ * return `{ ticket: string }`. Defaults to `"/ws-ticket"`, matching the
1635
+ * established backend convention (see up-miniapps-backend's
1636
+ * `GetWSTicket`). Pass `null` to disable ticket auth entirely — only
1637
+ * appropriate for a mini-app whose backend has no ws-ticket route yet,
1638
+ * or a genuinely public route (see `requireAuth` below).
1639
+ */
1640
+ ticketPath?: string | null;
1641
+ /**
1642
+ * Raw JWT to use when NOT running inside the native shell (standalone
1643
+ * dev browser) — the consuming app reads its own build-time
1644
+ * `import.meta.env.VITE_DEV_TOKEN` and passes it here; the SDK itself
1645
+ * has no access to a consuming app's Vite env (this package is bundled
1646
+ * by tsup, not Vite). Only ever used outside the shell — a ticket is
1647
+ * always preferred when `isRunningInsideNativeShell()` is true.
1648
+ */
1649
+ devToken?: string | null;
1650
+ /**
1651
+ * Overrides the resolved WS URL outright, for a mini-app whose WS
1652
+ * gateway genuinely lives somewhere `api.wsUrl` doesn't cover. Most
1653
+ * consumers should leave this unset and rely on `api.wsUrl`.
1654
+ */
1655
+ baseUrl?: string;
1656
+ /**
1657
+ * Extra static query params appended to every connection URL. Browsers
1658
+ * (and this WebView) give a raw `WebSocket` no custom-header API — a
1659
+ * query param is the closest equivalent, e.g. for a custom auth scheme
1660
+ * on top of the ticket/token this already sends.
1661
+ */
1662
+ query?: Record<string, string>;
1663
+ /**
1664
+ * When unset/`true` (the default), `connect()` refuses — warns and
1665
+ * delays rather than opening an unauthenticated socket — if neither a
1666
+ * ticket nor `devToken` is available. This is a deliberate safety net:
1667
+ * a channel-scoped app (ticket-authorized per-room access) accidentally
1668
+ * shipping with no auth configured would silently become an open room,
1669
+ * not a loud failure.
1670
+ *
1671
+ * Set to `false` only for a backend route that's genuinely public with
1672
+ * no per-connection identity at all — e.g. up-miniapps-by-night's `/ws`,
1673
+ * which has no auth middleware by design, broadcasts the same public
1674
+ * data to every connection, and has no rooms to gate. `connect()` then
1675
+ * opens the socket with no ticket/token query param whatsoever. Pair
1676
+ * with `ticketPath: null` (no point minting a ticket nobody checks).
1677
+ */
1678
+ requireAuth?: boolean;
1679
+ }
1680
+ /** Options for the channel-scoped mode — pass `channelField` to subscribe/unsubscribe to specific rooms. */
1681
+ interface CreateChannelWsServiceOptions<TMessage extends WsMessage = WsMessage> extends BaseWsServiceOptions<TMessage> {
1682
+ /**
1683
+ * Name of the field on each message that identifies which "room"/channel
1684
+ * it belongs to (e.g. `"groupUuid"`, `"gameUuid"`). Also the field name
1685
+ * used in the `{action, [channelField]: id}` subscribe/unsubscribe wire
1686
+ * messages sent to the backend. Presence of this field is what selects
1687
+ * channel mode over broadcast mode — see `CreateBroadcastWsServiceOptions`.
1688
+ */
1689
+ channelField: string;
1690
+ }
1691
+ /** Options for broadcast mode — omit `channelField` for a single global socket with no room concept. */
1692
+ interface CreateBroadcastWsServiceOptions<TMessage extends WsMessage = WsMessage> extends BaseWsServiceOptions<TMessage> {
1693
+ channelField?: undefined;
1694
+ }
1695
+ type CreateWsServiceOptions<TMessage extends WsMessage = WsMessage> = CreateChannelWsServiceOptions<TMessage> | CreateBroadcastWsServiceOptions<TMessage>;
1696
+ interface WsServiceClient<TMessage extends WsMessage = WsMessage> {
1697
+ /** Opens the connection if not already open/connecting. Safe to call repeatedly. */
1698
+ connect(): Promise<void>;
1699
+ /** Closes the connection and cancels any pending reconnect — no further automatic reconnect happens until `connect()`/`subscribe()` is called again. */
1700
+ disconnect(): void;
1701
+ /**
1702
+ * Subscribes to every message for one channel id. Sends the shared
1703
+ * `{action: "subscribe", [channelField]: id}` wire message automatically
1704
+ * on the first subscriber for that channel (and `"unsubscribe"` on the
1705
+ * last one leaving), and implicitly calls `connect()`. Returns an
1706
+ * unsubscribe function. Multiple concurrent channel ids are fully
1707
+ * supported — each gets its own wire subscribe/unsubscribe message and
1708
+ * its own dispatch, independent of every other channel's subscribers.
1709
+ */
1710
+ subscribe(channelId: string, callback: (message: TMessage) => void): () => void;
1711
+ /**
1712
+ * Subscribes to every message this socket ever receives, regardless of
1713
+ * channel — no wire message is sent for this (there's nothing to
1714
+ * subscribe/unsubscribe to, it's just an additional tap on the same
1715
+ * stream). Runs *alongside* any active `subscribe()` channel
1716
+ * subscriptions, socket.io-style ("join specific rooms AND listen
1717
+ * globally at once") — a message matching an active channel id is
1718
+ * delivered to both that channel's subscribers and every `onMessage`
1719
+ * listener. Implicitly calls `connect()`. Returns an unsubscribe
1720
+ * function.
1721
+ */
1722
+ onMessage(callback: (message: TMessage) => void): () => void;
1723
+ }
1724
+ interface BroadcastWsServiceClient<TMessage extends WsMessage = WsMessage> {
1725
+ /** Opens the connection if not already open/connecting. Safe to call repeatedly. */
1726
+ connect(): Promise<void>;
1727
+ /** Closes the connection and cancels any pending reconnect. */
1728
+ disconnect(): void;
1729
+ /**
1730
+ * Subscribes to every message this socket ever receives — there's no
1731
+ * room/channel concept in broadcast mode, so no subscribe/unsubscribe
1732
+ * wire message is ever sent; the backend just pushes to every connected
1733
+ * client. Implicitly calls `connect()`. Returns an unsubscribe function.
1734
+ */
1735
+ onMessage(callback: (message: TMessage) => void): () => void;
1736
+ }
1737
+ /**
1738
+ * Creates a reconnecting, (usually) authenticated WebSocket client — the WS
1739
+ * counterpart to `createApiService`. Centralizes exactly what every
1740
+ * `up-miniapps-*` app was independently hand-rolling with small drifting
1741
+ * bugs (one app's WS URL was captured once at construction and never
1742
+ * recomputed for the whole session; another always used a dev-only token
1743
+ * even in production, so it could never actually authenticate on a real
1744
+ * device): live URL resolution, ticket-based production auth with a
1745
+ * standalone-dev-token fallback, and exponential-backoff reconnect.
1746
+ *
1747
+ * Two modes, selected by whether `channelField` is present:
1748
+ * - **Channel mode** (`channelField` set): room-scoped pub/sub, matches
1749
+ * up-miniapps-split/up-miniapps-cursor's group/game-room shape. Returns a
1750
+ * `WsServiceClient` with `subscribe(channelId, cb)` — multiple concurrent
1751
+ * channel ids are fully supported — PLUS `onMessage(cb)`, a catch-all that
1752
+ * runs alongside any active channel subscriptions (socket.io-style: join
1753
+ * specific rooms and listen globally at the same time, on one socket).
1754
+ * - **Broadcast mode** (`channelField` omitted): one global socket, no
1755
+ * rooms, no subscribe/unsubscribe wire protocol — every message goes to
1756
+ * every listener. Matches up-miniapps-by-night's public live-update feed
1757
+ * (reactions/flash/favorite counts), which has no per-room access to gate
1758
+ * in the first place. Returns a `BroadcastWsServiceClient` with only
1759
+ * `onMessage(cb)` (no `subscribe` — there's no channel id to subscribe to).
1760
+ *
1761
+ * Deliberately stays a thin transport in both modes, same boundary as
1762
+ * `createApiService`: message *content* interpretation (e.g. treating one
1763
+ * event name as carrying real payload while every other message is a bare
1764
+ * cache-invalidation ping) belongs in each app's own thin wrapper around
1765
+ * this, not here — apps differ enough in what they consider safe to expose
1766
+ * over WS that baking that policy into the SDK would be the wrong
1767
+ * boundary.
1768
+ */
1769
+ declare function createWsService<TMessage extends WsMessage = WsMessage>(options: CreateChannelWsServiceOptions<TMessage>): WsServiceClient<TMessage>;
1770
+ declare function createWsService<TMessage extends WsMessage = WsMessage>(options: CreateBroadcastWsServiceOptions<TMessage>): BroadcastWsServiceClient<TMessage>;
1771
+
1772
+ export { type ApiServiceClient, type BridgeMessageType, BridgeMocker, type BridgeMockerProps, type BroadcastWsServiceClient, type CenterCallbackParams, type CreateApiServiceOptions, type CreateBroadcastWsServiceOptions, type CreateChannelWsServiceOptions, type CreateWsServiceOptions, type CustomControl, DebugConsole, type DebugConsoleProps, type DeclarativeJourneyOptions, type DrawerState, type EtaData, type FidelityTransactionTypeType, type FlyToParams, GeolocationEvent, GlobalEvent, type HapticStyleType, type IBridgeEnvelope, type IContextState, type IEdgeInsets, type IFidelityBalance, type IFidelityHistoryParams, type IFidelityTransaction, type IGenerateQrCodeOptions, type IInitializationOptions, type IInitializationResponse, type ILocationData, type INetworkOptions, type INetworkResponse, type IPaymentRequest, type IPickImageResult, type ISaveImageResult, type IUserProfile, type LiveRoutingOptions, type LiveRoutingResult, type LocationStreamConfig, Map, MapControls, MapControlsContext, type MapControlsProps, type MapControlsState, type MapInsets, MapMenu, MapMenuDivider, type MapMenuDividerProps, MapMenuFooter, type MapMenuFooterProps, MapMenuItem, type MapMenuItemProps, type MapMenuProps, MapMenuTitle, type MapMenuTitleProps, type MapProps, type MenuItem, MiniAppSDK, MockLocationContext, type MockLocationContextType, type NetworkMethodType, type POI, QRScanner, type QRScannerProps, RouteLayer, type RouteLayerProps, RouteRequest, RouteResponse, RouteStep, type SDKEvent, SDKMapProvider, SDKProvider, type SDKProviderProps, type ScrollConfig, type StreamAccelerometerData, type StreamHeadingData, type StreamLocationData, type StreamUpdatePayload, type SwipeAxisPriority, SwipeCarousel, type SwipeCarouselItem, type SwipeCarouselProps, type SwipeCarouselVerticalDragConfig, UIEvent, type UseJourneyOptions, type UseJourneyResult, type UserLocationState, type WsMessage, type WsServiceClient, createApiService, createWsService, sdk as default, renderMap, sdk, useIsMocked, useJourney, useLiveRouting, useMapControls, useMockLocation, useRoute, useTrimmedRoute, useUserLocation };
package/dist/index.js CHANGED
@@ -62,6 +62,13 @@ var Button = React.forwardRef(
62
62
  );
63
63
  Button.displayName = "Button";
64
64
 
65
+ // src/utils/environment.ts
66
+ function isRunningInsideNativeShell() {
67
+ if (typeof window === "undefined") return false;
68
+ const w = window;
69
+ return w.__UP_REACT_NATIVE_APP__ === true || !!w.ReactNativeWebView;
70
+ }
71
+
65
72
  // src/bridge.ts
66
73
  var BridgeTimeoutError = class extends Error {
67
74
  };
@@ -234,8 +241,7 @@ var Bridge = class {
234
241
  * Determine environment. Check flag or cache result.
235
242
  */
236
243
  isNative() {
237
- if (typeof window === "undefined") return false;
238
- return window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView;
244
+ return isRunningInsideNativeShell();
239
245
  }
240
246
  };
241
247
 
@@ -1995,14 +2001,14 @@ var useUserLocation = () => {
1995
2001
  });
1996
2002
  const mapPayload = useCallback3((payload) => {
1997
2003
  const coords = payload.location?.coords || payload.coords;
1998
- const headingData = payload.heading || payload.coords;
2004
+ const headingData = payload.heading;
1999
2005
  const newCoords = coords ? [coords.longitude, coords.latitude] : null;
2000
2006
  let heading = null;
2001
2007
  if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
2002
2008
  heading = headingData.trueHeading;
2003
2009
  } else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
2004
2010
  heading = headingData.magneticHeading;
2005
- } else if (headingData?.heading !== null && headingData.heading >= 0) {
2011
+ } else if (headingData?.heading !== null && headingData?.heading >= 0) {
2006
2012
  heading = headingData.heading;
2007
2013
  }
2008
2014
  return {
@@ -2479,7 +2485,14 @@ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2479
2485
  var MockRadiusLayer = ({
2480
2486
  center,
2481
2487
  radiusMeters,
2482
- color = "#39ff88"
2488
+ // Neutral gray, not any one app's accent color — this debug aid is shared
2489
+ // across every mini-app (each with its own, different palette), so it
2490
+ // shouldn't default to a color that reads as "part of the app" (was
2491
+ // #39ff88, a neon green/yellow left over from an earlier shared theme,
2492
+ // reported as looking like an unwanted yellow border around the user's
2493
+ // position marker in up-miniapps-explore, which has since moved to a
2494
+ // pastel palette with no neon accents at all).
2495
+ color = "#e2e8f0"
2483
2496
  }) => {
2484
2497
  const data = useMemo4(
2485
2498
  () => buildGeoCircle(center, radiusMeters),
@@ -3474,7 +3487,7 @@ var loadImage2 = (src) => new Promise((resolve, reject) => {
3474
3487
  });
3475
3488
 
3476
3489
  // package.json
3477
- var version = "0.6.0";
3490
+ var version = "0.6.1";
3478
3491
 
3479
3492
  // src/sdk.ts
3480
3493
  function deepFreeze(obj) {
@@ -5071,6 +5084,141 @@ function createApiService(options) {
5071
5084
  };
5072
5085
  }
5073
5086
 
5087
+ // src/services/wsService.ts
5088
+ function createWsService(options) {
5089
+ const channelField = options.channelField;
5090
+ let ws = null;
5091
+ const subscriptions = /* @__PURE__ */ new Map();
5092
+ const globalListeners = /* @__PURE__ */ new Set();
5093
+ let reconnectTimeout = null;
5094
+ let reconnectDelay = 1e3;
5095
+ let isConnecting = false;
5096
+ const getWsUrl = () => options.baseUrl || options.api.wsUrl || "";
5097
+ const sendAction = (action, channelId) => {
5098
+ if (ws?.readyState === WebSocket.OPEN) {
5099
+ ws.send(JSON.stringify({ action, [channelField]: channelId }));
5100
+ }
5101
+ };
5102
+ const scheduleReconnect = () => {
5103
+ if (reconnectTimeout) clearTimeout(reconnectTimeout);
5104
+ reconnectTimeout = setTimeout(() => {
5105
+ reconnectDelay = Math.min(reconnectDelay * 2, 3e4);
5106
+ connect();
5107
+ }, reconnectDelay);
5108
+ };
5109
+ const connect = async () => {
5110
+ if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING || isConnecting) {
5111
+ return;
5112
+ }
5113
+ isConnecting = true;
5114
+ try {
5115
+ const wsUrl = getWsUrl();
5116
+ if (!wsUrl) {
5117
+ console.warn(
5118
+ "[WS] No WebSocket backend URL available, connection delayed."
5119
+ );
5120
+ isConnecting = false;
5121
+ return;
5122
+ }
5123
+ let authParams = {};
5124
+ if (isRunningInsideNativeShell() && options.ticketPath !== null) {
5125
+ const ticketResponse = await options.api.post(
5126
+ options.ticketPath ?? "/ws-ticket"
5127
+ );
5128
+ authParams = { ticket: ticketResponse.data.ticket };
5129
+ } else if (options.devToken) {
5130
+ const rawJwt = options.devToken.startsWith("Bearer ") ? options.devToken.slice(7) : options.devToken;
5131
+ authParams = { token: rawJwt };
5132
+ } else if (options.requireAuth === false) {
5133
+ } else {
5134
+ console.warn("[WS] No auth available, connection delayed.");
5135
+ isConnecting = false;
5136
+ return;
5137
+ }
5138
+ const params = new URLSearchParams({ ...options.query, ...authParams });
5139
+ const query = params.toString();
5140
+ ws = new WebSocket(query ? `${wsUrl}?${query}` : wsUrl);
5141
+ ws.onopen = () => {
5142
+ isConnecting = false;
5143
+ reconnectDelay = 1e3;
5144
+ if (channelField) {
5145
+ for (const channelId of subscriptions.keys()) {
5146
+ sendAction("subscribe", channelId);
5147
+ }
5148
+ }
5149
+ };
5150
+ ws.onmessage = (event) => {
5151
+ try {
5152
+ const payload = JSON.parse(event.data);
5153
+ if (channelField) {
5154
+ const channelId = payload[channelField];
5155
+ if (typeof channelId === "string") {
5156
+ const callbacks = subscriptions.get(channelId);
5157
+ if (callbacks) {
5158
+ for (const cb of callbacks) cb(payload);
5159
+ }
5160
+ }
5161
+ }
5162
+ for (const cb of globalListeners) cb(payload);
5163
+ } catch (err) {
5164
+ console.error("[WS] Failed to parse message:", err);
5165
+ }
5166
+ };
5167
+ ws.onclose = () => {
5168
+ isConnecting = false;
5169
+ scheduleReconnect();
5170
+ };
5171
+ ws.onerror = (err) => {
5172
+ console.error("[WS] error:", err);
5173
+ isConnecting = false;
5174
+ ws?.close();
5175
+ };
5176
+ } catch (err) {
5177
+ console.error("[WS] Failed to establish connection:", err);
5178
+ isConnecting = false;
5179
+ scheduleReconnect();
5180
+ }
5181
+ };
5182
+ const disconnect = () => {
5183
+ if (reconnectTimeout) {
5184
+ clearTimeout(reconnectTimeout);
5185
+ reconnectTimeout = null;
5186
+ }
5187
+ if (ws) {
5188
+ ws.onclose = null;
5189
+ ws.close();
5190
+ ws = null;
5191
+ }
5192
+ };
5193
+ const subscribe = (channelId, callback) => {
5194
+ const isNewChannel = !subscriptions.has(channelId);
5195
+ if (isNewChannel) subscriptions.set(channelId, /* @__PURE__ */ new Set());
5196
+ subscriptions.get(channelId)?.add(callback);
5197
+ if (isNewChannel) sendAction("subscribe", channelId);
5198
+ connect();
5199
+ return () => {
5200
+ const set = subscriptions.get(channelId);
5201
+ if (!set) return;
5202
+ set.delete(callback);
5203
+ if (set.size === 0) {
5204
+ subscriptions.delete(channelId);
5205
+ sendAction("unsubscribe", channelId);
5206
+ }
5207
+ };
5208
+ };
5209
+ const onMessage = (callback) => {
5210
+ globalListeners.add(callback);
5211
+ connect();
5212
+ return () => {
5213
+ globalListeners.delete(callback);
5214
+ };
5215
+ };
5216
+ if (channelField) {
5217
+ return { connect, disconnect, onMessage, subscribe };
5218
+ }
5219
+ return { connect, disconnect, onMessage };
5220
+ }
5221
+
5074
5222
  // src/index.ts
5075
5223
  var src_default = sdk;
5076
5224
  export {
@@ -5095,6 +5243,7 @@ export {
5095
5243
  SwipeCarousel,
5096
5244
  UIEvent,
5097
5245
  createApiService,
5246
+ createWsService,
5098
5247
  src_default as default,
5099
5248
  handleClearRoute,
5100
5249
  handleDrawRoute,