@10play/expo-air 0.3.2 → 0.3.3

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.
@@ -109,4 +109,20 @@ public class ExpoAirAppDelegateSubscriber: ExpoAppDelegateSubscriber {
109
109
 
110
110
  print("[expo-air] Bubble auto-injected (size: \(bubbleSize), color: \(bubbleColor), server: \(effectiveServerUrl))")
111
111
  }
112
+
113
+ // Handle device token for push notifications
114
+ public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
115
+ #if DEBUG
116
+ let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
117
+ let token = tokenParts.joined()
118
+ print("[expo-air] Device token received: \(token)")
119
+ UserDefaults.standard.set(token, forKey: "expo-air-device-token")
120
+ #endif
121
+ }
122
+
123
+ public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
124
+ #if DEBUG
125
+ print("[expo-air] Failed to register for remote notifications: \(error)")
126
+ #endif
127
+ }
112
128
  }
@@ -1,5 +1,6 @@
1
1
  #import "WidgetBridge.h"
2
2
  #import <ReactCommon/RCTTurboModule.h>
3
+ #import <UserNotifications/UserNotifications.h>
3
4
 
4
5
  // Extend WidgetBridge to conform to RCTTurboModule in .mm file
5
6
  @interface WidgetBridge () <RCTTurboModule>
@@ -61,4 +62,141 @@ RCT_EXPORT_METHOD(expand) {
61
62
  });
62
63
  }
63
64
 
65
+ RCT_EXPORT_METHOD(requestPushToken:(RCTPromiseResolveBlock)resolve
66
+ rejecter:(RCTPromiseRejectBlock)reject) {
67
+ NSLog(@"[WidgetBridge] requestPushToken called");
68
+
69
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
70
+
71
+ [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
72
+ completionHandler:^(BOOL granted, NSError * _Nullable error) {
73
+ if (error) {
74
+ NSLog(@"[WidgetBridge] Push permission error: %@", error);
75
+ reject(@"PERMISSION_ERROR", error.localizedDescription, error);
76
+ return;
77
+ }
78
+
79
+ if (!granted) {
80
+ NSLog(@"[WidgetBridge] Push permission denied");
81
+ resolve([NSNull null]);
82
+ return;
83
+ }
84
+
85
+ NSLog(@"[WidgetBridge] Push permission granted, registering for remote notifications");
86
+
87
+ dispatch_async(dispatch_get_main_queue(), ^{
88
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
89
+
90
+ // Wait for device token to be set by AppDelegate, then get Expo token
91
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
92
+ NSString *deviceToken = [[NSUserDefaults standardUserDefaults] stringForKey:@"expo-air-device-token"];
93
+
94
+ if (!deviceToken) {
95
+ NSLog(@"[WidgetBridge] No device token available yet");
96
+ resolve([NSNull null]);
97
+ return;
98
+ }
99
+
100
+ NSLog(@"[WidgetBridge] Got device token: %@", deviceToken);
101
+
102
+ // Get app info for Expo API
103
+ NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
104
+
105
+ // Get EAS project ID from expo config (stored in Info.plist by expo prebuild)
106
+ NSString *projectId = nil;
107
+ NSDictionary *expoConfig = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"EXUpdatesRuntimeVersion"];
108
+ if (!projectId) {
109
+ // Try getting from EASProjectId
110
+ projectId = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"EASProjectID"];
111
+ }
112
+ if (!projectId) {
113
+ // Fallback: use bundle ID as experience ID format
114
+ projectId = bundleId;
115
+ }
116
+
117
+ NSLog(@"[WidgetBridge] Using projectId: %@, bundleId: %@", projectId, bundleId);
118
+
119
+ // Call Expo's API to convert device token to Expo push token
120
+ [self getExpoPushTokenWithDeviceToken:deviceToken
121
+ projectId:projectId
122
+ bundleId:bundleId
123
+ completion:^(NSString *expoToken, NSError *error) {
124
+ if (expoToken) {
125
+ NSLog(@"[WidgetBridge] Got Expo push token: %@", expoToken);
126
+ [[NSUserDefaults standardUserDefaults] setObject:expoToken forKey:@"expo-air-expo-token"];
127
+ resolve(expoToken);
128
+ } else {
129
+ NSLog(@"[WidgetBridge] Failed to get Expo token: %@", error);
130
+ resolve([NSNull null]);
131
+ }
132
+ }];
133
+ });
134
+ });
135
+ }];
136
+ }
137
+
138
+ - (void)getExpoPushTokenWithDeviceToken:(NSString *)deviceToken
139
+ projectId:(NSString *)projectId
140
+ bundleId:(NSString *)bundleId
141
+ completion:(void (^)(NSString *expoToken, NSError *error))completion {
142
+ NSURL *url = [NSURL URLWithString:@"https://exp.host/--/api/v2/push/getExpoPushToken"];
143
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
144
+ request.HTTPMethod = @"POST";
145
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
146
+
147
+ // Create unique device ID (persistent per device)
148
+ NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:@"expo-air-device-id"];
149
+ if (!deviceId) {
150
+ deviceId = [[NSUUID UUID] UUIDString];
151
+ [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:@"expo-air-device-id"];
152
+ }
153
+
154
+ NSDictionary *body = @{
155
+ @"deviceToken": deviceToken,
156
+ @"type": @"apns",
157
+ @"development": @YES, // DEBUG mode
158
+ @"projectId": projectId ?: bundleId,
159
+ @"appId": bundleId,
160
+ @"deviceId": deviceId
161
+ };
162
+
163
+ NSError *jsonError;
164
+ request.HTTPBody = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError];
165
+ if (jsonError) {
166
+ completion(nil, jsonError);
167
+ return;
168
+ }
169
+
170
+ NSLog(@"[WidgetBridge] Calling Expo API with body: %@", body);
171
+
172
+ NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
173
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
174
+ if (error) {
175
+ completion(nil, error);
176
+ return;
177
+ }
178
+
179
+ NSError *parseError;
180
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
181
+ if (parseError) {
182
+ completion(nil, parseError);
183
+ return;
184
+ }
185
+
186
+ NSLog(@"[WidgetBridge] Expo API response: %@", json);
187
+
188
+ // Response format: { "data": { "expoPushToken": "ExponentPushToken[xxx]" } }
189
+ NSString *token = json[@"data"][@"expoPushToken"];
190
+ if (token) {
191
+ completion(token, nil);
192
+ } else {
193
+ NSError *noTokenError = [NSError errorWithDomain:@"WidgetBridge"
194
+ code:1
195
+ userInfo:@{NSLocalizedDescriptionKey: @"No token in response"}];
196
+ completion(nil, noTokenError);
197
+ }
198
+ }];
199
+ [task resume];
200
+ }
201
+
64
202
  @end
@@ -539,8 +539,6 @@ __d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',
539
539
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.ResponseArea=function(l){var c=l.messages,u=l.currentResponse,x=(0,t.useRef)(null);if((0,t.useEffect)(()=>{x.current?.scrollToEnd({animated:!0})},[c,u]),!(c.length>0||u.length>0))return(0,o.jsx)(n.View,{style:f.emptyContainer,children:(0,o.jsx)(n.Text,{style:f.emptyText,children:"Send a prompt to start coding with Claude"})});return(0,o.jsxs)(n.ScrollView,{ref:x,style:f.container,contentContainerStyle:f.content,showsVerticalScrollIndicator:!0,children:[c.map((t,n)=>(0,o.jsx)(s,{message:t},n)),u.length>0&&(0,o.jsxs)(n.View,{style:f.messageContainer,children:[(0,o.jsx)(n.Text,{style:f.responseText,children:u}),(0,o.jsx)(n.View,{style:f.cursor})]})]})};var t=r(d[0]),n=r(d[1]),o=r(d[2]);function s(t){var s=t.message;switch(s.type){case"stream":case"status":default:return null;case"tool":return(0,o.jsx)(u,{tool:s});case"result":return(0,o.jsx)(x,{result:s});case"error":return(0,o.jsx)(n.View,{style:f.errorContainer,children:(0,o.jsx)(n.Text,{style:f.errorText,children:s.message})});case"user_prompt":return(0,o.jsx)(l,{message:s});case"history_result":return(0,o.jsx)(c,{message:s})}}function l(t){var s=t.message;return(0,o.jsx)(n.View,{style:f.userPromptContainer,children:(0,o.jsx)(n.Text,{style:f.userPromptText,children:s.content})})}function c(t){var s=t.message;return(0,o.jsx)(n.View,{style:f.resultContainer,children:(0,o.jsx)(n.Text,{style:f.responseText,children:s.content})})}function u(t){var s=t.tool,l={started:"\u27f3",completed:"\u2713",failed:"\u2715"}[s.status],c={started:"#FF9500",completed:"#30D158",failed:"#FF453A"}[s.status],u=s.input?"string"==typeof s.input?s.input:JSON.stringify(s.input).substring(0,100):null;return(0,o.jsxs)(n.View,{style:f.toolContainer,children:[(0,o.jsxs)(n.View,{style:f.toolHeader,children:[(0,o.jsx)(n.Text,{style:[f.toolStatus,{color:c}],children:l}),(0,o.jsx)(n.Text,{style:f.toolName,children:s.toolName})]}),u&&(0,o.jsx)(n.Text,{style:f.toolInput,numberOfLines:2,children:u})]})}function x(t){var s=t.result;return!s.success&&s.error?(0,o.jsx)(n.View,{style:f.errorContainer,children:(0,o.jsx)(n.Text,{style:f.errorText,children:s.error})}):(0,o.jsxs)(n.View,{style:f.resultContainer,children:[s.result&&(0,o.jsx)(n.Text,{style:f.responseText,children:s.result}),(void 0!==s.costUsd||void 0!==s.durationMs)&&(0,o.jsxs)(n.Text,{style:f.metaText,children:[void 0!==s.durationMs&&`${s.durationMs}ms`,void 0!==s.costUsd&&void 0!==s.durationMs&&" \u2022 ",void 0!==s.costUsd&&`$${s.costUsd.toFixed(4)}`]})]})}var f=n.StyleSheet.create({container:{flex:1,backgroundColor:"#000"},content:{padding:16,paddingBottom:24},emptyContainer:{flex:1,alignItems:"center",justifyContent:"center",padding:24},emptyText:{color:"rgba(255,255,255,0.4)",fontSize:15,textAlign:"center",lineHeight:22},messageContainer:{flexDirection:"row",flexWrap:"wrap"},responseText:{color:"rgba(255,255,255,0.95)",fontSize:15,lineHeight:22},cursor:{width:2,height:18,backgroundColor:"#fff",marginLeft:2,opacity:.7},resultContainer:{marginTop:8},metaText:{color:"rgba(255,255,255,0.4)",fontSize:12,marginTop:10},errorContainer:{backgroundColor:"rgba(255,59,48,0.15)",borderRadius:12,padding:12,marginVertical:6},errorText:{color:"#FF6B6B",fontSize:14},toolContainer:{backgroundColor:"rgba(255,255,255,0.06)",borderRadius:12,padding:12,marginVertical:6},toolHeader:{flexDirection:"row",alignItems:"center"},toolStatus:{fontSize:15,marginRight:8},toolName:{color:"rgba(255,255,255,0.7)",fontSize:14,fontWeight:"500"},toolInput:{color:"rgba(255,255,255,0.4)",fontSize:12,marginTop:6,fontFamily:"monospace"},userPromptContainer:{backgroundColor:"rgba(0,122,255,0.15)",borderRadius:16,padding:12,marginVertical:8,alignSelf:"flex-end",maxWidth:"85%"},userPromptText:{color:"#fff",fontSize:15,lineHeight:20}})},487,[63,258,250]);
540
540
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.GitChangesTab=function(c){var f=c.changes,x=c.onDiscard;if(!(f.length>0))return(0,n.jsxs)(t.View,{style:s.emptyContainer,children:[(0,n.jsx)(t.Text,{style:s.emptyTitle,children:"No uncommitted changes"}),(0,n.jsx)(t.Text,{style:s.emptySubtext,children:"Your working directory is clean"})]});return(0,n.jsxs)(t.View,{style:s.container,children:[(0,n.jsx)(t.ScrollView,{style:s.fileList,contentContainerStyle:s.fileListContent,children:f.map((c,f)=>(0,n.jsxs)(t.View,{style:s.fileRow,children:[(0,n.jsx)(t.Text,{style:[s.statusIcon,{color:l[c.status]}],children:o[c.status]}),(0,n.jsx)(t.Text,{style:s.fileName,numberOfLines:1,children:c.file})]},`${c.file}-${f}`))}),(0,n.jsxs)(t.View,{style:s.footer,children:[(0,n.jsx)(t.TouchableOpacity,{style:s.discardButton,onPress:x,children:(0,n.jsx)(t.Text,{style:s.discardText,children:"Discard All Changes"})}),(0,n.jsx)(t.Text,{style:s.warningText,children:"Note: Discarding won't reset memory"})]})]})},r(d[0]);var t=r(d[1]),n=r(d[2]),o={added:"A",modified:"M",deleted:"D",renamed:"R",untracked:"?"},l={added:"#30D158",modified:"#FFD60A",deleted:"#FF453A",renamed:"#BF5AF2",untracked:"#8E8E93"};var s=t.StyleSheet.create({container:{flex:1,backgroundColor:"#000"},emptyContainer:{flex:1,backgroundColor:"#000",alignItems:"center",justifyContent:"center",padding:24},emptyTitle:{color:"rgba(255,255,255,0.6)",fontSize:16,fontWeight:"500",marginBottom:8},emptySubtext:{color:"rgba(255,255,255,0.4)",fontSize:14},fileList:{flex:1},fileListContent:{padding:16},fileRow:{flexDirection:"row",alignItems:"center",paddingVertical:8,borderBottomWidth:1,borderBottomColor:"rgba(255,255,255,0.06)"},statusIcon:{width:20,fontSize:13,fontWeight:"600",fontFamily:"Menlo"},fileName:{flex:1,color:"rgba(255,255,255,0.8)",fontSize:14,fontFamily:"Menlo"},footer:{padding:16,borderTopWidth:1,borderTopColor:"rgba(255,255,255,0.08)",alignItems:"center"},discardButton:{backgroundColor:"rgba(255,59,48,0.15)",borderRadius:12,paddingVertical:12,paddingHorizontal:24,marginBottom:8},discardText:{color:"#FF3B30",fontSize:15,fontWeight:"500"},warningText:{color:"rgba(255,255,255,0.3)",fontSize:12}})},488,[63,258,250]);
541
541
  __d(function(g,r,i,a,m,_e,d){"use strict";function t(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"WebSocketClient",{enumerable:!0,get:function(){return n}}),_e.getWebSocketClient=function(){return o},_e.createWebSocketClient=function(t){o&&o.disconnect();return o=new n(t)};var e=t(r(d[0])),s=t(r(d[1])),n=(function(){return(0,s.default)(function t(s){(0,e.default)(this,t),this.ws=null,this.reconnectAttempts=0,this.reconnectTimeout=null,this._status="disconnected",this.shouldReconnect=!0,this.options={reconnectInterval:3e3,maxReconnectAttempts:10,onConnect:()=>{},onDisconnect:()=>{},onMessage:()=>{},onError:()=>{},onStatusChange:()=>{},...s}},[{key:"status",get:function(){return this._status}},{key:"setStatus",value:function(t){this._status=t,this.options.onStatusChange(t)}},{key:"connect",value:function(){if(this.ws?.readyState!==WebSocket.OPEN){this.shouldReconnect=!0,this.setStatus("connecting");try{this.ws=new WebSocket(this.options.url),this.ws.onopen=()=>{this.reconnectAttempts=0,this.setStatus("connected"),this.options.onConnect()},this.ws.onclose=()=>{this.setStatus("disconnected"),this.options.onDisconnect(),this.attemptReconnect()},this.ws.onerror=()=>{var t=new Error("WebSocket error");this.options.onError(t)},this.ws.onmessage=t=>{try{var e=JSON.parse(t.data);this.handleMessage(e)}catch(t){console.error("[expo-air] Failed to parse message:",t)}}}catch(t){this.options.onError(t),this.attemptReconnect()}}}},{key:"handleMessage",value:function(t){"status"===t.type?"processing"===t.status?this.setStatus("processing"):"idle"!==t.status&&"connected"!==t.status||this.setStatus("connected"):"result"===t.type||"error"===t.type?this.setStatus("connected"):"stopped"!==t.type&&"session_cleared"!==t.type||this.setStatus("connected"),this.options.onMessage(t)}},{key:"attemptReconnect",value:function(){this.shouldReconnect&&(this.reconnectAttempts>=this.options.maxReconnectAttempts?console.log("[expo-air] Max reconnect attempts reached"):(this.reconnectTimeout&&clearTimeout(this.reconnectTimeout),this.reconnectAttempts++,console.log(`[expo-air] Reconnecting... (attempt ${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`),this.reconnectTimeout=setTimeout(()=>{this.connect()},this.options.reconnectInterval)))}},{key:"disconnect",value:function(){this.shouldReconnect=!1,this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(),this.ws=null),this.setStatus("disconnected")}},{key:"sendPrompt",value:function(t){if(this.ws&&this.ws.readyState===WebSocket.OPEN){var e={type:"prompt",content:t,id:Math.random().toString(36).substring(2,15)};this.ws.send(JSON.stringify(e)),this.setStatus("processing")}else this.options.onError(new Error("Not connected"))}},{key:"requestNewSession",value:function(){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify({type:"new_session"})):this.options.onError(new Error("Not connected"))}},{key:"requestStop",value:function(){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify({type:"stop"})):this.options.onError(new Error("Not connected"))}},{key:"requestDiscardChanges",value:function(){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify({type:"discard_changes"})):this.options.onError(new Error("Not connected"))}},{key:"sendPushToken",value:function(t){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify({type:"register_push_token",token:t}))}},{key:"isConnected",value:function(){return this.ws?.readyState===WebSocket.OPEN}}])})();var o=null},489,[8,9]);
542
- __d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',{value:!0}),_e.requestPushToken=function(){return c.apply(this,arguments)},_e.setupTapHandler=function(e){var t=null,s=!1;return(0,n.default)(function*(){try{var n=yield u();if(!n||s)return;t=n.addNotificationResponseReceivedListener(t=>{var n=t.notification.request.content.data;n?.source===o&&(console.log("[expo-air] Notification tapped:",n),e(n.promptId,n.success))})}catch(e){console.log("[expo-air] Notifications not available:",e)}})(),()=>{s=!0,t?.remove()}};var e,t=r(d[0]),n=(e=t)&&e.__esModule?e:{default:e},o="expo-air",s=null;function u(){return l.apply(this,arguments)}function l(){return(l=(0,n.default)(function*(){if(s)return s;try{return s=yield r(d[2])(d[1],d.paths,"expo-notifications")}catch{return null}})).apply(this,arguments)}function c(){return(c=(0,n.default)(function*(){try{var e=yield u();if(!e)return console.log("[expo-air] expo-notifications not available"),null;var t=(yield e.getPermissionsAsync()).status,n=t;if("granted"!==t)n=(yield e.requestPermissionsAsync()).status;if("granted"!==n)return console.log("[expo-air] Push notification permission denied"),null;var o=yield e.getExpoPushTokenAsync({projectId:void 0});return console.log("[expo-air] Push token:",o.data),o.data}catch(e){return console.error("[expo-air] Failed to get push token:",e),null}})).apply(this,arguments)}},490,[346,null,491]);
543
- __d(function(g,r,i,a,m,e,d){m.exports=r(d[0])},491,[492]);
544
- __d(function(g,r,i,a,m,e,d){var n=r(d[0]).default;function t(n,t){var o=g[`${__METRO_GLOBAL_PREFIX__}__loadBundleAsync`];if(null!=o){var u=String(n);if(null!=t){var s=t[u];if(null!=s)return o(s)}}}function o(n,o){var u=t(n,o),s=()=>r.importAll(n);return null!=u?u.then(s):s()}function u(n,t,o){return s.apply(this,arguments)}function s(){return(s=n(function*(n,t,u){return o(n,t)})).apply(this,arguments)}u.unstable_importMaybeSync=function(n,t){return o(n,t)},u.prefetch=function(n,o,u){t(n,o)?.then(()=>{},()=>{})},u.unstable_resolve=function(n,t){if(!t)throw new Error('Bundle splitting is required for Web Worker imports');var o=t[n];if(!o)throw new Error('Worker import is missing from split bundle paths: '+o);return o},u.unstable_createWorker=function(n,t){if('undefined'==typeof crossOriginIsolated||!crossOriginIsolated)return new Worker(n,t);try{var o=(u=n,`\n const ASYNC_WORKER_BASE = ${JSON.stringify(u)};\n const IMPORT_SCRIPTS = importScripts;\n const FETCH = fetch;\n const fromBaseURL = (input) => new URL(input, ASYNC_WORKER_BASE).href;\n self.fetch = function(input, init) {\n return FETCH(typeof input === 'string' ? fromBaseURL(input) : input, init);\n };\n self.importScripts = function(...urls) {\n return IMPORT_SCRIPTS.apply(self, urls.map(fromBaseURL));\n };\n importScripts(ASYNC_WORKER_BASE);\n `);return n=URL.createObjectURL(new Blob([o],{type:'text/javascript'})),new Worker(n,t)}finally{URL.revokeObjectURL(n)}var u},m.exports=u},492,[346]);
542
+ __d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',{value:!0}),_e.requestPushToken=function(){return u.apply(this,arguments)},_e.setupTapHandler=function(e){return console.log("[expo-air] setupTapHandler: tap handling managed by main app"),()=>{}};var e,n=r(d[0]),o=(e=n)&&e.__esModule?e:{default:e},t=r(d[1]).NativeModules.WidgetBridge;function u(){return(u=(0,o.default)(function*(){try{if(!t?.requestPushToken)return console.log("[expo-air] WidgetBridge.requestPushToken not available"),null;console.log("[expo-air] Requesting push token via native bridge");var e=yield t.requestPushToken();return e?(console.log("[expo-air] Got push token:",e),e):(console.log("[expo-air] Push notification permission denied or token unavailable"),null)}catch(e){return console.error("[expo-air] Failed to get push token:",e),null}})).apply(this,arguments)}},490,[346,258]);
545
543
  __r(1);
546
544
  __r(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10play/expo-air",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Vibe Coding for React-Native",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,60 +1,36 @@
1
1
  /**
2
2
  * Push notification service for expo-air widget.
3
- * Does NOT override existing app notification handlers.
4
- * Fails silently if push notifications aren't configured or expo-notifications isn't installed.
3
+ * Uses native WidgetBridge for permission requests since the widget runs in an
4
+ * isolated React Native runtime that can't access the main app's modules.
5
5
  *
6
- * Note: The widget is only shown in the host app's DEBUG mode (controlled by native code),
7
- * so we don't need __DEV__ checks here. The pre-built widget bundle is always a production
8
- * build (via expo export), so __DEV__ would always be false anyway.
6
+ * Note: The widget is only shown in the host app's DEBUG mode (controlled by native code).
9
7
  */
10
8
 
11
- const EXPO_AIR_SOURCE = "expo-air";
9
+ import { NativeModules } from "react-native";
12
10
 
13
- // Lazy-loaded notifications module (may not be available)
14
- let Notifications: typeof import("expo-notifications") | null = null;
15
-
16
- async function getNotifications(): Promise<typeof import("expo-notifications") | null> {
17
- if (Notifications) return Notifications;
18
- try {
19
- Notifications = await import("expo-notifications");
20
- return Notifications;
21
- } catch {
22
- return null;
23
- }
24
- }
11
+ const { WidgetBridge } = NativeModules;
25
12
 
26
13
  /**
27
- * Request push notification permissions and get Expo push token.
28
- * @returns Expo push token string or null if failed/denied
14
+ * Request push notification permissions and get push token via native bridge.
15
+ * @returns Push token string or null if failed/denied
29
16
  */
30
17
  export async function requestPushToken(): Promise<string | null> {
31
18
  try {
32
- const notif = await getNotifications();
33
- if (!notif) {
34
- console.log("[expo-air] expo-notifications not available");
19
+ if (!WidgetBridge?.requestPushToken) {
20
+ console.log("[expo-air] WidgetBridge.requestPushToken not available");
35
21
  return null;
36
22
  }
37
23
 
38
- const { status: existingStatus } = await notif.getPermissionsAsync();
39
-
40
- let finalStatus = existingStatus;
41
- if (existingStatus !== "granted") {
42
- const { status } = await notif.requestPermissionsAsync();
43
- finalStatus = status;
44
- }
24
+ console.log("[expo-air] Requesting push token via native bridge");
25
+ const token = await WidgetBridge.requestPushToken();
45
26
 
46
- if (finalStatus !== "granted") {
47
- console.log("[expo-air] Push notification permission denied");
27
+ if (token) {
28
+ console.log("[expo-air] Got push token:", token);
29
+ return token;
30
+ } else {
31
+ console.log("[expo-air] Push notification permission denied or token unavailable");
48
32
  return null;
49
33
  }
50
-
51
- // Get Expo push token
52
- const tokenData = await notif.getExpoPushTokenAsync({
53
- projectId: undefined, // Uses EAS projectId from app.json
54
- });
55
-
56
- console.log("[expo-air] Push token:", tokenData.data);
57
- return tokenData.data;
58
34
  } catch (error) {
59
35
  console.error("[expo-air] Failed to get push token:", error);
60
36
  return null;
@@ -63,49 +39,16 @@ export async function requestPushToken(): Promise<string | null> {
63
39
 
64
40
  /**
65
41
  * Setup tap handler for expo-air notifications.
66
- * Uses addNotificationResponseReceivedListener (additive, non-overriding).
67
- * Filters by data.source === "expo-air" to ignore other app notifications.
42
+ * Note: Notification tap handling requires expo-notifications in the main app.
43
+ * This is a no-op in the widget since we can't access expo-notifications.
68
44
  * @param onTap Callback when user taps an expo-air notification
69
45
  * @returns Cleanup function to remove listener
70
46
  */
71
47
  export function setupTapHandler(
72
48
  onTap: (promptId?: string, success?: boolean) => void
73
49
  ): () => void {
74
-
75
- // Start async setup, return cleanup that handles pending setup
76
- let subscription: { remove: () => void } | null = null;
77
- let cancelled = false;
78
-
79
- (async () => {
80
- try {
81
- const notif = await getNotifications();
82
- if (!notif || cancelled) return;
83
-
84
- subscription = notif.addNotificationResponseReceivedListener(
85
- (response) => {
86
- const data = response.notification.request.content.data as {
87
- source?: string;
88
- promptId?: string;
89
- success?: boolean;
90
- };
91
-
92
- // Only handle expo-air notifications
93
- if (data?.source !== EXPO_AIR_SOURCE) {
94
- return;
95
- }
96
-
97
- console.log("[expo-air] Notification tapped:", data);
98
- onTap(data.promptId, data.success);
99
- }
100
- );
101
- } catch (error) {
102
- // Fail silently if notifications aren't configured
103
- console.log("[expo-air] Notifications not available:", error);
104
- }
105
- })();
106
-
107
- return () => {
108
- cancelled = true;
109
- subscription?.remove();
110
- };
50
+ // Notification tap handling would require expo-notifications which isn't
51
+ // available in the widget's isolated runtime. The main app handles this.
52
+ console.log("[expo-air] setupTapHandler: tap handling managed by main app");
53
+ return () => {};
111
54
  }