diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e4c74bf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + + - name: Install dependencies + run: yarn install --immutable + + - name: Type check + run: yarn typecheck + + - name: Build + run: yarn build diff --git a/examples/app.json b/examples/app.json index 61a5300..c0ade5f 100644 --- a/examples/app.json +++ b/examples/app.json @@ -3,7 +3,7 @@ "name": "Userback SDK Example", "slug": "userback-sdk-example", "version": "1.0.0", - "orientation": "portrait", + "orientation": "default", "platforms": [ "ios", "android" diff --git a/examples/ios/UserbackSDKExample.xcodeproj/project.pbxproj b/examples/ios/UserbackSDKExample.xcodeproj/project.pbxproj index a7d936a..8a117cb 100644 --- a/examples/ios/UserbackSDKExample.xcodeproj/project.pbxproj +++ b/examples/ios/UserbackSDKExample.xcodeproj/project.pbxproj @@ -365,7 +365,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "UserbackSDKExample/UserbackSDKExample-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -392,7 +392,7 @@ PRODUCT_NAME = UserbackSDKExample; SWIFT_OBJC_BRIDGING_HEADER = "UserbackSDKExample/UserbackSDKExample-Bridging-Header.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/examples/ios/UserbackSDKExample/Info.plist b/examples/ios/UserbackSDKExample/Info.plist index 82c2764..ada64d5 100644 --- a/examples/ios/UserbackSDKExample/Info.plist +++ b/examples/ios/UserbackSDKExample/Info.plist @@ -51,13 +51,15 @@ arm64 UIRequiresFullScreen - + UIStatusBarStyle UIStatusBarStyleDefault UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad diff --git a/src/UserbackProvider.tsx b/src/UserbackProvider.tsx index 7b3c35a..9ed74a8 100644 --- a/src/UserbackProvider.tsx +++ b/src/UserbackProvider.tsx @@ -108,11 +108,14 @@ export function UserbackProvider({ children }: UserbackProviderProps) { useEffect(() => { const onStart = (cfg: UserbackConfig) => setConfig({ ...cfg }); const onStop = () => setConfig(null); + const onForceClose = () => setWidgetOpen(false); UserbackSDK.on('_start', onStart); UserbackSDK.on('_stop', onStop); + UserbackSDK.on('_forceClose', onForceClose); return () => { UserbackSDK.off('_start', onStart); UserbackSDK.off('_stop', onStop); + UserbackSDK.off('_forceClose', onForceClose); }; }, []); @@ -142,6 +145,13 @@ export function UserbackProvider({ children }: UserbackProviderProps) { webViewRef.current?.injectJavaScript(js); }, []); + useEffect(() => { + const sub = Dimensions.addEventListener('change', () => { + inject(`(function(){window.dispatchEvent(new CustomEvent('userback:rotate'));})();true;`); + }); + return () => sub.remove(); + }, [inject]); + useEffect(() => { if (config) { UserbackSDK._attach(inject); diff --git a/src/UserbackSDK.ts b/src/UserbackSDK.ts index 2d3a980..1d31e2e 100644 --- a/src/UserbackSDK.ts +++ b/src/UserbackSDK.ts @@ -36,6 +36,7 @@ class UserbackSDKClass extends Emitter { private _ready = false; private _pending: string[] = []; private _inject: ((js: string) => void) | null = null; + private _formOpenTimeout: ReturnType | null = null; public onWidgetConfigLoaded?: (config: Record) => void; public onWidgetResize?: (size: { width: number; height: number }) => void; @@ -105,6 +106,7 @@ class UserbackSDKClass extends Emitter { break; case 'widget_resize': + this._clearFormOpenTimeout(); this.onWidgetResize?.(data.payload ?? {}); break; @@ -191,14 +193,21 @@ class UserbackSDKClass extends Emitter { } } + private _clearFormOpenTimeout(): void { + if (this._formOpenTimeout !== null) { + clearTimeout(this._formOpenTimeout); + this._formOpenTimeout = null; + } + } + private _sendNativeEvent(event: Record): void { if (!this._inject) return; if (event.type === 'log') { - const detail = { payload: { type: event.level, message: event.message } }; + const detail = { payload: { type: event.level, message: event.message, mobileSDK: true }, mobileSDK: true }; const js = `(function(){window.dispatchEvent(new CustomEvent('userback:nativeLogEvent',{detail:${JSON.stringify(detail)}}));})();true;`; this._inject(js); } else { - const detail = { payload: event }; + const detail = { payload: { ...event, mobileSDK: true }, mobileSDK: true }; const js = `(function(){window.dispatchEvent(new CustomEvent('userback:nativeNetworkEvent',{detail:${JSON.stringify(detail)}}));})();true;`; this._inject(js); } @@ -206,11 +215,8 @@ class UserbackSDKClass extends Emitter { /** @internal — called by UserbackProvider after capturing the screenshot */ _sendScreenshot(dataURL: string): void { - const message = JSON.stringify({ - type: 'native_screenshot', - payload: { data_url: dataURL }, - }); - const js = `(function(){window.dispatchEvent(new CustomEvent('userback:nativeScreenshot',{detail:${message}}));})();true;`; + const detail = { type: 'native_screenshot', payload: { data_url: dataURL, mobileSDK: true }, mobileSDK: true }; + const js = `(function(){window.dispatchEvent(new CustomEvent('userback:nativeScreenshot',{detail:${JSON.stringify(detail)}}));})();true;`; if (this._inject) this._inject(js); } @@ -234,6 +240,7 @@ class UserbackSDKClass extends Emitter { } stop(): void { + this._clearFormOpenTimeout(); LogObserver.stop(); NetworkObserver.stop(); this._config = null; @@ -263,7 +270,13 @@ class UserbackSDKClass extends Emitter { } openForm(mode = '', directTo?: string): void { + this._clearFormOpenTimeout(); this._run('openForm', [mode, directTo ?? null]); + this._formOpenTimeout = setTimeout(() => { + if (__DEV__) console.log('[Userback] openForm timed out — widget did not respond.'); + this._clearFormOpenTimeout(); + this.emit('_forceClose'); + }, 5000); } openPortal(): void { this._run('openPortal'); } diff --git a/yarn.lock b/yarn.lock index ba57f14..b4ffa91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2186,7 +2186,6 @@ __metadata: resolution: "@userback/react-native-sdk@workspace:." dependencies: "@types/react": "npm:^18.0.0" - expo: "npm:~52.0.49" react: "npm:18.3.1" react-native: "npm:0.76.9" react-native-view-shot: "npm:^3.8.0" @@ -3816,7 +3815,7 @@ __metadata: languageName: node linkType: hard -"expo@npm:~52.0.0, expo@npm:~52.0.49": +"expo@npm:~52.0.0": version: 52.0.49 resolution: "expo@npm:52.0.49" dependencies: