shopsense-mobile 0.1.0 → 0.1.2
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 +3 -8
- package/package.json +1 -1
- package/src/PersistentWebView.tsx +11 -13
package/README.md
CHANGED
|
@@ -32,11 +32,7 @@ Add the package to your monorepo and import from it. Peer deps must exist in the
|
|
|
32
32
|
import { ShopSenseRoot } from "shopsense-widget";
|
|
33
33
|
|
|
34
34
|
export default function RootLayout() {
|
|
35
|
-
return
|
|
36
|
-
<ShopSenseRoot>
|
|
37
|
-
{/* your app routes */}
|
|
38
|
-
</ShopSenseRoot>
|
|
39
|
-
);
|
|
35
|
+
return <ShopSenseRoot>{/* your app routes */}</ShopSenseRoot>;
|
|
40
36
|
}
|
|
41
37
|
```
|
|
42
38
|
|
|
@@ -60,7 +56,7 @@ export default function SearchScreen() {
|
|
|
60
56
|
return (
|
|
61
57
|
<View style={{ flex: 1, minHeight: 0 }}>
|
|
62
58
|
<ShopSenseWidget
|
|
63
|
-
zoneId={
|
|
59
|
+
zoneId={"xyz"}
|
|
64
60
|
// Optional: fail fast if the widget doesn't become ready (default: 10000ms)
|
|
65
61
|
statusTimeoutMs={10000}
|
|
66
62
|
onProductSelect={(product) => {
|
|
@@ -113,5 +109,4 @@ If `onProductSelect` is **not** provided, the package opens:
|
|
|
113
109
|
|
|
114
110
|
Your app must register the deep link scheme for this to work.
|
|
115
111
|
|
|
116
|
-
[cursor-integration]: https://cursor.com/link/prompt?text=Task%3A%20Integrate%20the%20%60shopsense-widget%60%20package%20into%20this%20Expo%20(expo-router)%20React%20Native%20app.%0A%0AFirst%20open%20and%20follow%20%60packages%2Fshopsense-widget%2FCURSOR_INTEGRATION_PROMPT.md%60%20in%20the%20workspace.%20Implement%20all%20steps%20there.%0A%0A---%20Reference%20(same%20as%20that%20file)%20---%0A%23%20Cursor%20Agent%3A%20Integrate%20%60shopsense-widget%60%20(Expo%20%2F%20React%20Native)%0A%0APaste%20this%20file%20into%20**Cursor%20Agent**%2C%20or%20click%20**Open%20in%20Cursor**%20in%20%60README.md%60%20to%20load%20the%20same%20instructions%20via%20Cursor%E2%80%99s%20prompt%20deeplink%20(%60cursor.com%2Flink%2Fprompt%60).%0A%0A%23%23%20Context%0A%0A%60shopsense-widget%60%20provides%20%60ShopSenseRoot%60%2C%20%60ShopSenseWidget%60%2C%20and%20%60PersistentWebView%60%20for%20a%20ShopSense%20WebView%20shell.%20With%20%60ShopSenseRoot%60%20at%20the%20app%20root%2C%20%60ShopSenseWidget%60%20registers%20config%20and%20renders%20**nothing**%3B%20the%20visible%20UI%20is%20%60PersistentWebView%60.%0A%0A%23%23%20Requirements%0A%0A1.%20**Dependencies**%20%20%0A%20%20%20Ensure%20the%20app%20depends%20on%20%60shopsense-widget%60%2C%20%60react-native-webview%60%2C%20and%20%60expo-router%60%20(peer%20deps).%0A%0A2.%20**Root%20layout**%20%20%0A%20%20%20-%20Wrap%20the%20app%20in%20%60ShopSenseRoot%60%20once.%20%20%0A%20%20%20-%20Do%20**not**%20place%20%60PersistentWebView%60%20as%20a%20direct%20**flex%20sibling**%20of%20the%20Expo%20%60Stack%60%20at%20root%20%E2%80%94%20both%20would%20use%20%60flex%3A%201%60%20and%20split%20the%20screen%20~50%2F50.%20Either%20remove%20root%20%60PersistentWebView%60%20and%20mount%20it%20on%20the%20search%20screen%20below%20the%20app%20bar%2C%20or%20use%20a%20different%20layout%20that%20does%20not%20share%20vertical%20flex%20with%20the%20navigator.%0A%0A3.%20**Search%20screen**%20%20%0A%20%20%20-%20Use%20your%20existing%20screen%20wrapper%20(e.g.%20%60RootLayout%60%20with%20%60showScrollView%3D%7Bfalse%7D%60).%20%20%0A%20%20%20-%20Ensure%20the%20non-scroll%20branch%20wraps%20children%20in%20%60View%60%20with%20%60%7B%20flex%3A%201%2C%20minHeight%3A%200%20%7D%60%20so%20height%20flows%20to%20children.%20%20%0A%20%20%20-%20Inside%20that%20screen%2C%20render%3A%0A%20%20%20%20%20-%20%60ShopSenseWidget%60%20with%
|
|
117
|
-
|
|
112
|
+
[cursor-integration]: https://cursor.com/link/prompt?text=Task%3A%20Integrate%20the%20%60shopsense-widget%60%20package%20into%20this%20Expo%20(expo-router)%20React%20Native%20app.%0A%0AFirst%20open%20and%20follow%20%60packages%2Fshopsense-widget%2FCURSOR_INTEGRATION_PROMPT.md%60%20in%20the%20workspace.%20Implement%20all%20steps%20there.%0A%0A---%20Reference%20(same%20as%20that%20file)%20---%0A%23%20Cursor%20Agent%3A%20Integrate%20%60shopsense-widget%60%20(Expo%20%2F%20React%20Native)%0A%0APaste%20this%20file%20into%20**Cursor%20Agent**%2C%20or%20click%20**Open%20in%20Cursor**%20in%20%60README.md%60%20to%20load%20the%20same%20instructions%20via%20Cursor%E2%80%99s%20prompt%20deeplink%20(%60cursor.com%2Flink%2Fprompt%60).%0A%0A%23%23%20Context%0A%0A%60shopsense-widget%60%20provides%20%60ShopSenseRoot%60%2C%20%60ShopSenseWidget%60%2C%20and%20%60PersistentWebView%60%20for%20a%20ShopSense%20WebView%20shell.%20With%20%60ShopSenseRoot%60%20at%20the%20app%20root%2C%20%60ShopSenseWidget%60%20registers%20config%20and%20renders%20**nothing**%3B%20the%20visible%20UI%20is%20%60PersistentWebView%60.%0A%0A%23%23%20Requirements%0A%0A1.%20**Dependencies**%20%20%0A%20%20%20Ensure%20the%20app%20depends%20on%20%60shopsense-widget%60%2C%20%60react-native-webview%60%2C%20and%20%60expo-router%60%20(peer%20deps).%0A%0A2.%20**Root%20layout**%20%20%0A%20%20%20-%20Wrap%20the%20app%20in%20%60ShopSenseRoot%60%20once.%20%20%0A%20%20%20-%20Do%20**not**%20place%20%60PersistentWebView%60%20as%20a%20direct%20**flex%20sibling**%20of%20the%20Expo%20%60Stack%60%20at%20root%20%E2%80%94%20both%20would%20use%20%60flex%3A%201%60%20and%20split%20the%20screen%20~50%2F50.%20Either%20remove%20root%20%60PersistentWebView%60%20and%20mount%20it%20on%20the%20search%20screen%20below%20the%20app%20bar%2C%20or%20use%20a%20different%20layout%20that%20does%20not%20share%20vertical%20flex%20with%20the%20navigator.%0A%0A3.%20**Search%20screen**%20%20%0A%20%20%20-%20Use%20your%20existing%20screen%20wrapper%20(e.g.%20%60RootLayout%60%20with%20%60showScrollView%3D%7Bfalse%7D%60).%20%20%0A%20%20%20-%20Ensure%20the%20non-scroll%20branch%20wraps%20children%20in%20%60View%60%20with%20%60%7B%20flex%3A%201%2C%20minHeight%3A%200%20%7D%60%20so%20height%20flows%20to%20children.%20%20%0A%20%20%20-%20Inside%20that%20screen%2C%20render%3A%0A%20%20%20%20%20-%20%60ShopSenseWidget%60%20with%20zoneId%60%2C%20%60baseUrl%60%2C%20%60apiBase%60%2C%20and%20optional%20callbacks%20(%60onProductSelect%60%2C%20%60onDebugMessage%60).%20%20%0A%20%20%20%20%20-%20%60PersistentWebView%60%20as%20a%20**sibling**%20in%20a%20%60View%60%20with%20%60%7B%20flex%3A%201%2C%20minHeight%3A%200%20%7D%60%20so%20the%20WebView%20fills%20the%20area%20**below**%20the%20app%20bar.%0A%20%20%20-%20Implement%20**fallback**%20(native%20search)%20using%20widget-driven%20status%3A%0A%20%20%20%20%20-%20The%20package%20emits%20%60WIDGET_STATUS%60%20and%20%60WEBVIEW_*%60%20events%20via%20%60onDebugMessage%60.%0A%20%20%20%20%20-%20Use%20%60statusTimeoutMs%60%20(default%20%6010000%60)%20so%20a%20slow%2Fdown%20server%20fails%20fast%20and%20your%20UI%20can%20switch%20to%20fallback.%0A%20%20%20%20%20-%20When%20%60WIDGET_STATUS.payload.state%20%3D%3D%3D%20%22failed%22%60%20(including%20timeout)%20or%20when%20%60WEBVIEW_ERROR%2FWEBVIEW_HTTP_ERROR%60%20occurs%2C%20conditionally%20render%20your%20fallback%20screen%20(e.g.%20%60app%2Fapp%2Fsearch.jsx%60)%20instead%20of%20leaving%20the%20user%20stuck.%0A%0A4.%20**Callbacks**%20%20%0A%20%20%20Wire%20%60onProductSelect%60%20to%20your%20navigation%20or%20cart%20flow%3B%20optionally%20log%20%60onDebugMessage%60%20during%20development.%0A%0A5.%20**Verify**%20%20%0A%20%20%20Open%20the%20search%20route%3A%20the%20WebView%20should%20fill%20the%20full%20height%20below%20the%20header%20with%20no%20large%20empty%20band%20above%20the%20widget.%0A%0A%23%23%20Minimal%20screen%20shape%20(illustrative)%0A%0A%60%60%60tsx%0Aimport%20%7B%20PersistentWebView%2C%20ShopSenseWidget%20%7D%20from%20%22shopsense-widget%22%3B%0Aimport%20%7B%20View%20%7D%20from%20%22react-native%22%3B%0A%0A%2F%2F%20Inside%20your%20layout%20component%20that%20already%20renders%20the%20app%20bar%3A%0A%3CView%20style%3D%7B%7B%20flex%3A%201%2C%20minHeight%3A%200%20%7D%7D%3E%0A%20%20%3CShopSenseWidget%20%20%20%20zoneId%3D%7BYOUR_ZONE_ID%7D%0A%20%20%20%20baseUrl%3D%22https%3A%2F%2Fyour-widget-host%22%0A%20%20%20%20apiBase%3D%22https%3A%2F%2Fyour-widget-host%2Fapi%2Fapi%22%0A%20%20%20%20onProductSelect%3D%7B(product)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%2F*%20navigate%20or%20handle%20*%2F%0A%20%20%20%20%7D%7D%0A%20%20%2F%3E%0A%20%20%3CPersistentWebView%20%2F%3E%0A%3C%2FView%3E%0A%60%60%60%0A%0AAdjust%20imports%20and%20layout%20names%20to%20match%20your%20app.%0A%0A%23%23%20Reference%20implementation%20(copy%2Fpaste)%0A%0AThis%20is%20a%20working%20example%20from%20%60app%2Fapp%2Fshopsense-search.jsx%60%20showing%3A%0A%0A-%20Passing%20%60extraParams%60%20only%20when%20logged%20in%0A-%20Using%20%60statusTimeoutMs%60%20to%20fail%20fast%0A-%20Conditional%20rendering%20of%20%60app%2Fapp%2Fsearch.jsx%60%20as%20fallback%20when%20the%20widget%20fails%0A%0A%60%60%60jsx%0Aimport%20%7B%20router%20%7D%20from%20%22expo-router%22%3B%0Aimport%20React%2C%20%7B%20useCallback%2C%20useMemo%2C%20useState%20%7D%20from%20%22react%22%3B%0Aimport%20%7B%20View%20%7D%20from%20%22react-native%22%3B%0Aimport%20%7B%20useSelector%20%7D%20from%20%22react-redux%22%3B%0Aimport%20%7B%20PersistentWebView%2C%20ShopSenseWidget%20%7D%20from%20%22shopsense-widget%22%3B%0Aimport%20RootLayout%20from%20%22..%2Fsrc%2Fmodules%2Fshared%2FRootLayout%2FRootLayout%22%3B%0Aimport%20%7B%20SCREENS%20%7D%20from%20%22..%2Fsrc%2Fmodules%2Fshared%2Fconstants%22%3B%0Aimport%20%7B%20getStateId%20%7D%20from%20%22..%2Fsrc%2Fmodules%2Fshared%2Futils%22%3B%0Aimport%20SearchFallback%20from%20%22.%2Fsearch%22%3B%0A%0Aexport%20default%20function%20ShopSenseSearchScreen()%20%7B%0A%20%20const%20token%20%3D%20useSelector((state)%20%3D%3E%20state%3F.authReducer%3F.tokens%3F.accessToken)%3B%0A%20%20const%20user%20%3D%20useSelector((state)%20%3D%3E%20state%3F.authReducer%3F.user)%3B%0A%20%20const%20%5BshowFallback%2C%20setShowFallback%5D%20%3D%20useState(false)%3B%0A%0A%20%20const%20extraParams%20%3D%20useMemo(()%20%3D%3E%20%7B%0A%20%20%20%20if%20(!token)%20return%20undefined%3B%0A%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20customerTier%3A%20user%3F.tier%2C%0A%20%20%20%20%20%20pricingTier%3A%20user%3F.pricingTier%2C%0A%20%20%20%20%20%20stateId%3A%20getStateId%3F.(user)%20%3F%3F%20undefined%2C%0A%20%20%20%20%20%20viewSpecificCategory%3A%20user%3F.viewSpecificCategory%2C%0A%20%20%20%20%20%20viewSpecificProduct%3A%20user%3F.viewSpecificProduct%2C%0A%20%20%20%20%7D%3B%0A%20%20%7D%2C%20%5Btoken%2C%20user%5D)%3B%0A%0A%20%20const%20handleDebugMessage%20%3D%20useCallback((msg)%20%3D%3E%20%7B%0A%20%20%20%20if%20(msg%3F.type%20%3D%3D%3D%20%22WIDGET_STATUS%22)%20%7B%0A%20%20%20%20%20%20const%20state%20%3D%20msg%3F.payload%3F.state%3B%0A%20%20%20%20%20%20if%20(state%20%3D%3D%3D%20%22failed%22)%20%7B%0A%20%20%20%20%20%20%20%20setShowFallback(true)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(state%20%3D%3D%3D%20%22succeeded%22)%20%7B%0A%20%20%20%20%20%20%20%20setShowFallback(false)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(msg%3F.type%20%3D%3D%3D%20%22WEBVIEW_ERROR%22%20%7C%7C%20msg%3F.type%20%3D%3D%3D%20%22WEBVIEW_HTTP_ERROR%22)%20%7B%0A%20%20%20%20%20%20setShowFallback(true)%3B%0A%20%20%20%20%7D%0A%20%20%7D%2C%20%5B%5D)%3B%0A%0A%20%20if%20(showFallback)%20%7B%0A%20%20%20%20return%20%3CSearchFallback%20%2F%3E%3B%0A%20%20%7D%0A%0A%20%20return%20(%0A%20%20%20%20%3CRootLayout%20showScrollView%3D%7Bfalse%7D%20title%3D%22Search%22%3E%0A%20%20%20%20%20%20%3CView%20style%3D%7B%7B%20flex%3A%201%2C%20minHeight%3A%200%20%7D%7D%3E%0A%20%20%20%20%20%20%20%20%3CShopSenseWidget%0A%20%20%20%20%20%20%20%20%20%20%22%0A%20%20%20%20%20%20%20%20%20%20zoneId%3D%7Bxyz%7D%0A%20%20%20%20%20%20%20%20%20%20extraParams%3D%7BextraParams%7D%0A%20%20%20%20%20%20%20%20%20%20statusTimeoutMs%3D%7B10000%7D%0A%20%20%20%20%20%20%20%20%20%20onProductSelect%3D%7B()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20router.push(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20pathname%3A%20SCREENS.PRODUCT_DETAILS%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20params%3A%20%7B%20id%3A%20product.productId%2C%20alias%3A%20product.alias%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%7D%0A%20%20%20%20%20%20%20%20%20%20onDebugMessage%3D%7BhandleDebugMessage%7D%0A%20%20%20%20%20%20%20%20%2F%3E%0A%0A%20%20%20%20%20%20%20%20%3CPersistentWebView%20%2F%3E%0A%20%20%20%20%20%20%3C%2FView%3E%0A%20%20%20%20%3C%2FRootLayout%3E%0A%20%20)%3B%0A%7D%0A%60%60%60%0A%0A---%0A%0AIf%20you%20change%20this%20file%2C%20update%20the%20%60%5Bcursor-integration%5D%60%20link%20at%20the%20bottom%20of%20%60README.md%60%20(re-encode%20the%20same%20%60Task%3A%60%20%2B%20reference%20block%20with%20%60encodeURIComponent%60%20for%20%60https%3A%2F%2Fcursor.com%2Flink%2Fprompt%3Ftext%3D%E2%80%A6%60).%0A
|
package/package.json
CHANGED
|
@@ -4,9 +4,8 @@ import type { WebViewMessageEvent } from "react-native-webview";
|
|
|
4
4
|
import { WebView } from "react-native-webview";
|
|
5
5
|
import { useShopSense } from "./context";
|
|
6
6
|
import {
|
|
7
|
-
extractProductSelect,
|
|
8
7
|
isDebugMessage,
|
|
9
|
-
tryParseJsonMessage
|
|
8
|
+
tryParseJsonMessage
|
|
10
9
|
} from "./message";
|
|
11
10
|
import { defaultOpenProduct } from "./navigation";
|
|
12
11
|
import { buildWidgetUri } from "./url";
|
|
@@ -43,11 +42,10 @@ export const PersistentWebView = memo(function PersistentWebView() {
|
|
|
43
42
|
if (!wv?.injectJavaScript) return;
|
|
44
43
|
|
|
45
44
|
// When returning from a native screen (Product → Back), the WebView doesn't get a
|
|
46
|
-
// browser "pageshow" event.
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// want to restore the already-rendered view/state.
|
|
45
|
+
// browser "pageshow" event. Re-show the existing container so results re-appear
|
|
46
|
+
// without requiring typing. Do NOT dispatch layout events here — dispatching
|
|
47
|
+
// `lookin:layout2` would switch Layout 1 (fullscreen) into Layout 2 mode, revealing
|
|
48
|
+
// the host search bar. Do NOT dispatch `hashchange` — that forces a refetch.
|
|
51
49
|
wv.injectJavaScript(
|
|
52
50
|
`(function(){try{
|
|
53
51
|
var el = document.getElementById('lookin-widget-container');
|
|
@@ -56,10 +54,10 @@ export const PersistentWebView = memo(function PersistentWebView() {
|
|
|
56
54
|
el.style.visibility = 'visible';
|
|
57
55
|
el.style.opacity = '1';
|
|
58
56
|
}
|
|
59
|
-
|
|
60
|
-
}catch(e){};return true;})();`
|
|
57
|
+
}catch(e){};return true;})();`
|
|
61
58
|
);
|
|
62
59
|
}, [visible, webViewRef]);
|
|
60
|
+
// document.dispatchEvent(new CustomEvent('lookin:layout2'));
|
|
63
61
|
|
|
64
62
|
const handleMessage = useCallback(
|
|
65
63
|
(event: WebViewMessageEvent) => {
|
|
@@ -91,13 +89,13 @@ export const PersistentWebView = memo(function PersistentWebView() {
|
|
|
91
89
|
lastWebViewUrlRef.current ?? (o?.payload as any)?.productUrl ?? null;
|
|
92
90
|
|
|
93
91
|
const payload = o?.payload;
|
|
94
|
-
const product = extractProductSelect(payload);
|
|
92
|
+
// const product = extractProductSelect(payload);
|
|
95
93
|
|
|
96
|
-
if (
|
|
94
|
+
if (payload) {
|
|
97
95
|
if (callbacksRef.current.onProductSelect) {
|
|
98
|
-
callbacksRef.current.onProductSelect(
|
|
96
|
+
callbacksRef.current.onProductSelect(payload);
|
|
99
97
|
} else {
|
|
100
|
-
defaultOpenProduct(
|
|
98
|
+
defaultOpenProduct(payload);
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
}
|