@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
|
}
|
package/ios/WidgetBridge.mm
CHANGED
|
@@ -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
|
package/ios/widget.jsbundle
CHANGED
|
@@ -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
|
|
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,60 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push notification service for expo-air widget.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
|
|
9
|
+
import { NativeModules } from "react-native";
|
|
12
10
|
|
|
13
|
-
|
|
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
|
|
28
|
-
* @returns
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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 (
|
|
47
|
-
console.log("[expo-air]
|
|
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
|
-
*
|
|
67
|
-
*
|
|
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
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
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
|
}
|