@cloudparse/up-miniapps-sdk 0.5.7 → 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.js CHANGED
@@ -62,7 +62,16 @@ 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
73
+ var BridgeTimeoutError = class extends Error {
74
+ };
66
75
  var Bridge = class {
67
76
  callbacks = /* @__PURE__ */ new Map();
68
77
  eventListeners = /* @__PURE__ */ new Map();
@@ -133,18 +142,32 @@ var Bridge = class {
133
142
  * @param type The action type (e.g., "GET_LOCATION").
134
143
  * @param payload Data associated with the action.
135
144
  * @param timeoutMs Time in milliseconds to wait for a response before rejecting.
145
+ * @param retries Number of additional attempts if a request times out (default 1 — one retry).
146
+ * Exists because the response for a request can be lost in transit — e.g. the host's
147
+ * injected response script running before this WebView's own `message` listener has
148
+ * attached, worse under rapid mount/unmount cycles — with no application-visible symptom
149
+ * other than the full 30s timeout. A blind retry self-heals that class of loss without
150
+ * needing to diagnose which specific transport race dropped the original message; every
151
+ * call site benefits automatically instead of each consumer app hand-rolling its own
152
+ * retry-after-catch logic (which previously only covered `initialize()`, and imperfectly).
136
153
  * @returns A promise that resolves with the native response.
137
154
  */
138
- send(type, payload, timeoutMs = 3e4) {
155
+ send(type, payload, timeoutMs = 3e4, retries = 1) {
139
156
  if (!this.isNative()) {
140
157
  return this.handleMockMessage(type, payload);
141
158
  }
159
+ return this.sendOnce(type, payload, timeoutMs).catch((err) => {
160
+ if (retries <= 0 || !(err instanceof BridgeTimeoutError)) throw err;
161
+ return this.send(type, payload, timeoutMs, retries - 1);
162
+ });
163
+ }
164
+ sendOnce(type, payload, timeoutMs) {
142
165
  return new Promise((resolve, reject) => {
143
166
  const callbackId = `${type}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
144
167
  const timeout = setTimeout(() => {
145
168
  if (this.callbacks.has(callbackId)) {
146
169
  this.callbacks.delete(callbackId);
147
- reject(new Error(`Bridge request timeout: ${type}`));
170
+ reject(new BridgeTimeoutError(`Bridge request timeout: ${type}`));
148
171
  }
149
172
  }, timeoutMs);
150
173
  this.callbacks.set(callbackId, {
@@ -218,8 +241,7 @@ var Bridge = class {
218
241
  * Determine environment. Check flag or cache result.
219
242
  */
220
243
  isNative() {
221
- if (typeof window === "undefined") return false;
222
- return window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView;
244
+ return isRunningInsideNativeShell();
223
245
  }
224
246
  };
225
247
 
@@ -1979,14 +2001,14 @@ var useUserLocation = () => {
1979
2001
  });
1980
2002
  const mapPayload = useCallback3((payload) => {
1981
2003
  const coords = payload.location?.coords || payload.coords;
1982
- const headingData = payload.heading || payload.coords;
2004
+ const headingData = payload.heading;
1983
2005
  const newCoords = coords ? [coords.longitude, coords.latitude] : null;
1984
2006
  let heading = null;
1985
2007
  if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
1986
2008
  heading = headingData.trueHeading;
1987
2009
  } else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
1988
2010
  heading = headingData.magneticHeading;
1989
- } else if (headingData?.heading !== null && headingData.heading >= 0) {
2011
+ } else if (headingData?.heading !== null && headingData?.heading >= 0) {
1990
2012
  heading = headingData.heading;
1991
2013
  }
1992
2014
  return {
@@ -2463,7 +2485,14 @@ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2463
2485
  var MockRadiusLayer = ({
2464
2486
  center,
2465
2487
  radiusMeters,
2466
- 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"
2467
2496
  }) => {
2468
2497
  const data = useMemo4(
2469
2498
  () => buildGeoCircle(center, radiusMeters),
@@ -3458,7 +3487,7 @@ var loadImage2 = (src) => new Promise((resolve, reject) => {
3458
3487
  });
3459
3488
 
3460
3489
  // package.json
3461
- var version = "0.5.7";
3490
+ var version = "0.6.1";
3462
3491
 
3463
3492
  // src/sdk.ts
3464
3493
  function deepFreeze(obj) {
@@ -3473,6 +3502,7 @@ function deepFreeze(obj) {
3473
3502
  return Object.freeze(obj);
3474
3503
  }
3475
3504
  var globalRoutingConfig = null;
3505
+ var lastKnownEnv = null;
3476
3506
  var MiniAppSDK = class {
3477
3507
  /**
3478
3508
  * Map services.
@@ -3795,7 +3825,14 @@ var MiniAppSDK = class {
3795
3825
  });
3796
3826
  constructor() {
3797
3827
  this.bridge = new Bridge();
3828
+ this.bridge.on(
3829
+ "UPDATE_CONTEXT" /* UPDATE_CONTEXT */,
3830
+ (data) => {
3831
+ if (data?.env) lastKnownEnv = data.env;
3832
+ }
3833
+ );
3798
3834
  this.initialize = this.initialize.bind(this);
3835
+ this.getEnv = this.getEnv.bind(this);
3799
3836
  this.on = this.on.bind(this);
3800
3837
  this.off = this.off.bind(this);
3801
3838
  }
@@ -3819,8 +3856,22 @@ var MiniAppSDK = class {
3819
3856
  * The starting point for the Mini-App. Must be called before any other SDK methods.
3820
3857
  * @param options The options to provide to the shell app
3821
3858
  */
3822
- initialize(options) {
3823
- return this.bridge.send("INITIALIZE", options);
3859
+ async initialize(options) {
3860
+ const res = await this.bridge.send(
3861
+ "INITIALIZE",
3862
+ options
3863
+ );
3864
+ if (res?.env) lastKnownEnv = res.env;
3865
+ return res;
3866
+ }
3867
+ /**
3868
+ * Returns the last known `env` (shell-provided config like `backendUrl`),
3869
+ * refreshed automatically by `initialize()` and every `UPDATE_CONTEXT`
3870
+ * push — read this instead of caching your own copy of `env` values.
3871
+ * Returns `null` before the first successful `initialize()`/context push.
3872
+ */
3873
+ getEnv() {
3874
+ return lastKnownEnv;
3824
3875
  }
3825
3876
  /**
3826
3877
  * Registers a callback for a specific event from the native shell.
@@ -4967,6 +5018,207 @@ var SwipeCarousel = ({
4967
5018
  );
4968
5019
  };
4969
5020
 
5021
+ // src/services/apiService.ts
5022
+ function buildUrl(baseUrl, path) {
5023
+ if (path.startsWith("http://") || path.startsWith("https://")) {
5024
+ return path;
5025
+ }
5026
+ let cleanBase = baseUrl.trim();
5027
+ while (cleanBase.endsWith("/")) {
5028
+ cleanBase = cleanBase.slice(0, -1);
5029
+ }
5030
+ let cleanPath = path.trim();
5031
+ if (!cleanPath.startsWith("/")) {
5032
+ cleanPath = `/${cleanPath}`;
5033
+ }
5034
+ const baseEndsWithV1 = cleanBase.endsWith("/api/v1");
5035
+ const pathStartsWithV1 = cleanPath.startsWith("/api/v1/") || cleanPath === "/api/v1";
5036
+ if (baseEndsWithV1 && pathStartsWithV1) {
5037
+ const strippedPath = cleanPath.slice(7);
5038
+ return `${cleanBase}${strippedPath}`;
5039
+ }
5040
+ return `${cleanBase}${cleanPath}`;
5041
+ }
5042
+ function getEarlyBackendUrl(kind) {
5043
+ if (typeof window === "undefined") return void 0;
5044
+ const early = window.__UP_BACKEND_URLS__;
5045
+ return early?.[kind] ?? void 0;
5046
+ }
5047
+ function createApiService(options) {
5048
+ const getBaseUrl = () => getEarlyBackendUrl("http") || sdk.getEnv()?.backendUrl || options.defaultBaseUrl;
5049
+ const getWsUrl = () => getEarlyBackendUrl("ws") || sdk.getEnv()?.wsBackendUrl || options.defaultWsBackendUrl;
5050
+ return {
5051
+ get baseUrl() {
5052
+ return getBaseUrl();
5053
+ },
5054
+ delete: (path) => sdk.network.request({
5055
+ method: "DELETE",
5056
+ url: buildUrl(getBaseUrl(), path)
5057
+ }),
5058
+ get: (path) => sdk.network.request({
5059
+ headers: { "Content-Type": "application/json" },
5060
+ method: "GET",
5061
+ url: buildUrl(getBaseUrl(), path)
5062
+ }),
5063
+ graphqlQuery: (query, variables = {}) => sdk.network.request({
5064
+ body: { query, variables },
5065
+ headers: { "Content-Type": "application/json" },
5066
+ method: "POST",
5067
+ url: buildUrl(getBaseUrl(), "/graphql")
5068
+ }),
5069
+ patch: (path, body) => sdk.network.request({
5070
+ body,
5071
+ headers: { "Content-Type": "application/json" },
5072
+ method: "PATCH",
5073
+ url: buildUrl(getBaseUrl(), path)
5074
+ }),
5075
+ post: (path, body) => sdk.network.request({
5076
+ body,
5077
+ headers: { "Content-Type": "application/json" },
5078
+ method: "POST",
5079
+ url: buildUrl(getBaseUrl(), path)
5080
+ }),
5081
+ get wsUrl() {
5082
+ return getWsUrl();
5083
+ }
5084
+ };
5085
+ }
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
+
4970
5222
  // src/index.ts
4971
5223
  var src_default = sdk;
4972
5224
  export {
@@ -4990,6 +5242,8 @@ export {
4990
5242
  SDKProvider,
4991
5243
  SwipeCarousel,
4992
5244
  UIEvent,
5245
+ createApiService,
5246
+ createWsService,
4993
5247
  src_default as default,
4994
5248
  handleClearRoute,
4995
5249
  handleDrawRoute,