@cloudparse/up-miniapps-sdk 0.6.6 → 0.6.8

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.cjs CHANGED
@@ -267,11 +267,61 @@ var Bridge = class {
267
267
  }
268
268
  }
269
269
  /**
270
- * Internal handler for mock message processing, overrideable in test environments
270
+ * Internal handler for mock message processing, overrideable in test environments.
271
+ *
272
+ * Every mini-app that calls a bridge method also has to run standalone in a plain
273
+ * browser (dev, and this vault's own Chrome-based verification) with no native shell
274
+ * attached — `isNative()` is false, so `send()` never round-trips through the real
275
+ * host at all. Before this switch existed, EVERY unhandled type here resolved with
276
+ * plain `undefined` (see the `default` branch below), which silently broke any caller
277
+ * checking a result shape like `{success: boolean}` — `undefined?.success` reads as
278
+ * `false`, not "no answer", so e.g. `sdk.media.saveImage()` looked like a real
279
+ * permission-denied rejection in the browser instead of "there's no native shell
280
+ * here to ask." Add a `case` here — instead of leaving every mini-app to
281
+ * individually special-case "what happens when I call this with no shell attached" —
282
+ * whenever a bridge method's mock behavior should be more than a no-op.
271
283
  */
272
284
  handleMockMessage(type, payload) {
273
- console.warn(`[Bridge] Mock mode active: Unhandled message type ${type}`);
274
- return Promise.resolve();
285
+ switch (type) {
286
+ case "SAVE_IMAGE":
287
+ return this.mockSaveImage(
288
+ payload
289
+ );
290
+ default:
291
+ console.warn(
292
+ `[Bridge] Mock mode active: Unhandled message type ${type}`
293
+ );
294
+ return Promise.resolve();
295
+ }
296
+ }
297
+ /**
298
+ * Standalone-browser stand-in for the native `SAVE_IMAGE` handler: triggers a real
299
+ * browser download (an `<a download>` click) instead of writing to a device photo
300
+ * library, which doesn't exist here. Resolves `{success: true}` on a real trigger so
301
+ * every mini-app's own `result?.success` check gets the same shape/semantics as the
302
+ * real native response — no per-app "am I in mock mode" branching needed. Consumers
303
+ * can still keep their own `catch` fallback for the genuinely different case of a
304
+ * native shell that's missing its WebView bridge (see `sendOnce`'s own rejection
305
+ * path) — that's a real native edge case, not mocking, and this method never runs
306
+ * for it (`send()` only reaches `handleMockMessage` when `!isNative()`).
307
+ */
308
+ mockSaveImage(payload) {
309
+ if (typeof document === "undefined") {
310
+ return Promise.resolve({ success: false });
311
+ }
312
+ try {
313
+ const href = payload.base64.startsWith("data:") ? payload.base64 : `data:image/png;base64,${payload.base64}`;
314
+ const link = document.createElement("a");
315
+ link.href = href;
316
+ link.download = payload.filename ? `${payload.filename}.png` : "image.png";
317
+ document.body.appendChild(link);
318
+ link.click();
319
+ link.remove();
320
+ return Promise.resolve({ success: true });
321
+ } catch (err) {
322
+ console.warn("[Bridge] Mock SAVE_IMAGE download failed:", err);
323
+ return Promise.resolve({ success: false });
324
+ }
275
325
  }
276
326
  sendOnce(type, payload, timeoutMs) {
277
327
  return new Promise((resolve, reject) => {
@@ -735,7 +785,7 @@ var MapDrawer = (0, import_react2.forwardRef)(
735
785
  const dragDelta = touchEndY - dragStartY;
736
786
  const drawerHeightFromBottom = dragBaseHeightPxRef.current - dragDelta;
737
787
  const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
738
- let targetState;
788
+ let targetState2;
739
789
  if (currentState === "auto") {
740
790
  const autoMaxPx2 = resolveHeightToPixels(
741
791
  drawerAutoMaxHeight || "100%",
@@ -743,22 +793,22 @@ var MapDrawer = (0, import_react2.forwardRef)(
743
793
  );
744
794
  const autoPx2 = Math.min(autoMaxPx2, autoContentPx);
745
795
  const distToAuto = Math.abs(drawerHeightFromBottom - autoPx2);
746
- targetState = distToAuto <= distToClosed ? "auto" : "closed";
796
+ targetState2 = distToAuto <= distToClosed ? "auto" : "closed";
747
797
  } else {
748
798
  const distToFull = Math.abs(drawerHeightFromBottom - fullPx2);
749
799
  const distToPeek = Math.abs(drawerHeightFromBottom - peekPx2);
750
800
  const minDist = Math.min(distToFull, distToPeek, distToClosed);
751
801
  if (minDist === distToFull) {
752
- targetState = "full";
802
+ targetState2 = "full";
753
803
  } else if (minDist === distToPeek) {
754
- targetState = "peek";
804
+ targetState2 = "peek";
755
805
  } else {
756
- targetState = "closed";
806
+ targetState2 = "closed";
757
807
  }
758
808
  }
759
809
  setTouchStartY(null);
760
810
  setCurrentTouchY(null);
761
- onChangeState(targetState);
811
+ onChangeState(targetState2);
762
812
  requestAnimationFrame(() => {
763
813
  if (parentContainer) {
764
814
  parentContainer.style.setProperty("--drag-offset", "0px");
@@ -2035,115 +2085,142 @@ var GlobalEvent = /* @__PURE__ */ ((GlobalEvent2) => {
2035
2085
  // src/components/Map/hooks/useUserLocation.ts
2036
2086
  var import_react6 = require("react");
2037
2087
  var LERP_FACTOR = 0.12;
2038
- var useUserLocation = () => {
2039
- const [locationState, setLocationState] = (0, import_react6.useState)({
2040
- accuracy: null,
2041
- coords: null,
2042
- heading: null
2043
- });
2044
- const targetRef = (0, import_react6.useRef)({
2045
- accuracy: null,
2046
- coords: null,
2047
- heading: null
2048
- });
2049
- const currentRef = (0, import_react6.useRef)({
2050
- accuracy: null,
2051
- coords: null,
2052
- heading: null
2053
- });
2054
- const mapPayload = (0, import_react6.useCallback)((payload) => {
2055
- const coords = payload.location?.coords || payload.coords;
2056
- const headingData = payload.heading;
2057
- const newCoords = coords ? [coords.longitude, coords.latitude] : null;
2058
- let heading = null;
2059
- if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
2060
- heading = headingData.trueHeading;
2061
- } else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
2062
- heading = headingData.magneticHeading;
2063
- } else if (headingData?.heading !== null && headingData?.heading >= 0) {
2064
- heading = headingData.heading;
2065
- }
2066
- return {
2067
- accuracy: coords?.accuracy || null,
2068
- coords: newCoords,
2069
- heading
2070
- };
2071
- }, []);
2072
- (0, import_react6.useEffect)(() => {
2073
- let rafId;
2074
- const animate = () => {
2075
- let changed = false;
2076
- if (targetRef.current.coords) {
2077
- if (!currentRef.current.coords) {
2078
- currentRef.current.coords = [...targetRef.current.coords];
2079
- changed = true;
2080
- } else {
2081
- const [tLng, tLat] = targetRef.current.coords;
2082
- const [cLng, cLat] = currentRef.current.coords;
2083
- const nLng = cLng + (tLng - cLng) * LERP_FACTOR;
2084
- const nLat = cLat + (tLat - cLat) * LERP_FACTOR;
2085
- if (Math.abs(nLng - cLng) > 1e-7 || Math.abs(nLat - cLat) > 1e-7) {
2086
- currentRef.current.coords = [nLng, nLat];
2087
- changed = true;
2088
- }
2089
- }
2090
- }
2091
- if (targetRef.current.heading !== null) {
2092
- if (currentRef.current.heading === null) {
2093
- currentRef.current.heading = targetRef.current.heading;
2094
- changed = true;
2095
- } else {
2096
- const target = targetRef.current.heading;
2097
- const current = currentRef.current.heading;
2098
- let delta = target - current;
2099
- if (delta > 180) delta -= 360;
2100
- if (delta < -180) delta += 360;
2101
- const nHeading = current + delta * LERP_FACTOR;
2102
- const normalized = (nHeading % 360 + 360) % 360;
2103
- if (Math.abs(delta) > 0.1) {
2104
- currentRef.current.heading = normalized;
2105
- changed = true;
2106
- }
2107
- }
2108
- }
2109
- if (changed) {
2110
- setLocationState({
2111
- accuracy: targetRef.current.accuracy,
2112
- coords: currentRef.current.coords ? [...currentRef.current.coords] : null,
2113
- heading: currentRef.current.heading
2114
- });
2115
- }
2116
- rafId = requestAnimationFrame(animate);
2117
- };
2118
- rafId = requestAnimationFrame(animate);
2119
- sdk.location.getCurrentPosition().then((data) => {
2120
- const payload = mapPayload(data);
2121
- targetRef.current = payload;
2122
- }).catch((e) => console.warn("Initial location fetch failed", e));
2123
- const unsubscribeStream = sdk.on(
2124
- "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
2125
- (payload) => {
2126
- targetRef.current = mapPayload(payload);
2088
+ var EMPTY_STATE = {
2089
+ accuracy: null,
2090
+ coords: null,
2091
+ heading: null
2092
+ };
2093
+ function mapPayload(payload) {
2094
+ const coords = payload.location?.coords || payload.coords;
2095
+ const headingData = payload.heading;
2096
+ const newCoords = coords ? [coords.longitude, coords.latitude] : null;
2097
+ let heading = null;
2098
+ if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
2099
+ heading = headingData.trueHeading;
2100
+ } else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
2101
+ heading = headingData.magneticHeading;
2102
+ } else if (headingData?.heading !== null && headingData?.heading >= 0) {
2103
+ heading = headingData.heading;
2104
+ }
2105
+ return {
2106
+ accuracy: coords?.accuracy || null,
2107
+ coords: newCoords,
2108
+ heading
2109
+ };
2110
+ }
2111
+ var refCount = 0;
2112
+ var generation = 0;
2113
+ var subscribers = /* @__PURE__ */ new Set();
2114
+ var targetState = { ...EMPTY_STATE };
2115
+ var currentInterpolated = { coords: null, heading: null };
2116
+ var rafId = null;
2117
+ var unsubscribeStream = null;
2118
+ var unsubscribeLegacy = null;
2119
+ function getSnapshot() {
2120
+ return {
2121
+ accuracy: targetState.accuracy,
2122
+ coords: currentInterpolated.coords ? [...currentInterpolated.coords] : null,
2123
+ heading: currentInterpolated.heading
2124
+ };
2125
+ }
2126
+ function notify() {
2127
+ const snapshot = getSnapshot();
2128
+ for (const callback of subscribers) callback(snapshot);
2129
+ }
2130
+ function animate() {
2131
+ let changed = false;
2132
+ if (targetState.coords) {
2133
+ if (!currentInterpolated.coords) {
2134
+ currentInterpolated.coords = [...targetState.coords];
2135
+ changed = true;
2136
+ } else {
2137
+ const [tLng, tLat] = targetState.coords;
2138
+ const [cLng, cLat] = currentInterpolated.coords;
2139
+ const nLng = cLng + (tLng - cLng) * LERP_FACTOR;
2140
+ const nLat = cLat + (tLat - cLat) * LERP_FACTOR;
2141
+ if (Math.abs(nLng - cLng) > 1e-7 || Math.abs(nLat - cLat) > 1e-7) {
2142
+ currentInterpolated.coords = [nLng, nLat];
2143
+ changed = true;
2127
2144
  }
2128
- );
2129
- const unsubscribeLegacy = sdk.on(
2130
- "LOCATION_UPDATE" /* LOCATION_UPDATE */,
2131
- (payload) => {
2132
- targetRef.current = mapPayload(payload);
2145
+ }
2146
+ }
2147
+ if (targetState.heading !== null) {
2148
+ if (currentInterpolated.heading === null) {
2149
+ currentInterpolated.heading = targetState.heading;
2150
+ changed = true;
2151
+ } else {
2152
+ const target = targetState.heading;
2153
+ const current = currentInterpolated.heading;
2154
+ let delta = target - current;
2155
+ if (delta > 180) delta -= 360;
2156
+ if (delta < -180) delta += 360;
2157
+ const nHeading = current + delta * LERP_FACTOR;
2158
+ const normalized = (nHeading % 360 + 360) % 360;
2159
+ if (Math.abs(delta) > 0.1) {
2160
+ currentInterpolated.heading = normalized;
2161
+ changed = true;
2133
2162
  }
2134
- );
2135
- sdk.location.startStream({
2136
- distanceFilter: 1,
2137
- headingThreshold: 2
2138
- }).catch((e) => console.warn("Failed to start location stream", e));
2163
+ }
2164
+ }
2165
+ if (changed) notify();
2166
+ rafId = requestAnimationFrame(animate);
2167
+ }
2168
+ function startSharedStream() {
2169
+ rafId = requestAnimationFrame(animate);
2170
+ generation++;
2171
+ const thisGeneration = generation;
2172
+ sdk.location.getCurrentPosition().then((data) => {
2173
+ if (thisGeneration !== generation) return;
2174
+ targetState = mapPayload(data);
2175
+ }).catch((e) => console.warn("Initial location fetch failed", e));
2176
+ unsubscribeStream = sdk.on(
2177
+ "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
2178
+ (payload) => {
2179
+ targetState = mapPayload(payload);
2180
+ }
2181
+ );
2182
+ unsubscribeLegacy = sdk.on(
2183
+ "LOCATION_UPDATE" /* LOCATION_UPDATE */,
2184
+ (payload) => {
2185
+ targetState = mapPayload(payload);
2186
+ }
2187
+ );
2188
+ sdk.location.startStream({
2189
+ distanceFilter: 1,
2190
+ headingThreshold: 2
2191
+ }).catch((e) => console.warn("Failed to start location stream", e));
2192
+ }
2193
+ function stopSharedStream() {
2194
+ generation++;
2195
+ if (rafId !== null) cancelAnimationFrame(rafId);
2196
+ rafId = null;
2197
+ unsubscribeStream?.();
2198
+ unsubscribeLegacy?.();
2199
+ unsubscribeStream = null;
2200
+ unsubscribeLegacy = null;
2201
+ sdk.location.stopStream().catch(() => {
2202
+ });
2203
+ targetState = { ...EMPTY_STATE };
2204
+ currentInterpolated = { coords: null, heading: null };
2205
+ }
2206
+ var useUserLocation = () => {
2207
+ const [locationState, setLocationState] = (0, import_react6.useState)(getSnapshot);
2208
+ (0, import_react6.useEffect)(() => {
2209
+ refCount++;
2210
+ if (refCount === 1) {
2211
+ startSharedStream();
2212
+ } else {
2213
+ setLocationState(getSnapshot());
2214
+ }
2215
+ subscribers.add(setLocationState);
2139
2216
  return () => {
2140
- cancelAnimationFrame(rafId);
2141
- unsubscribeStream();
2142
- unsubscribeLegacy();
2143
- sdk.location.stopStream().catch(() => {
2144
- });
2217
+ subscribers.delete(setLocationState);
2218
+ refCount--;
2219
+ if (refCount === 0) {
2220
+ stopSharedStream();
2221
+ }
2145
2222
  };
2146
- }, [mapPayload]);
2223
+ }, []);
2147
2224
  return locationState;
2148
2225
  };
2149
2226
 
@@ -3715,7 +3792,7 @@ var RoutingService = {
3715
3792
  };
3716
3793
 
3717
3794
  // package.json
3718
- var version = "0.6.6";
3795
+ var version = "0.6.8";
3719
3796
 
3720
3797
  // src/sdk.ts
3721
3798
  function deepFreeze(obj) {
@@ -4126,7 +4203,6 @@ var import_react14 = require("react");
4126
4203
  var import_jsx_runtime16 = require("react/jsx-runtime");
4127
4204
  var AVATAR_MOUTH_VARIANTS = [
4128
4205
  "variant03",
4129
- "variant06",
4130
4206
  "variant07",
4131
4207
  "variant08",
4132
4208
  "variant09",
@@ -4870,7 +4946,10 @@ var RESISTANCE = 0.5;
4870
4946
  var DRAG_DEADZONE = 8;
4871
4947
  var FLING_FRICTION = 0.95;
4872
4948
  var FLING_MIN_VELOCITY = 0.02;
4873
- function DefaultIndicator({ progress, isRefreshing }) {
4949
+ function DefaultIndicator({
4950
+ progress,
4951
+ isRefreshing
4952
+ }) {
4874
4953
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4875
4954
  "div",
4876
4955
  {