@entrig/react-native 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import UIKit
2
+ import UserNotifications
3
+ import EntrigSDK
4
+
5
+ /// Helper class that wires up the iOS AppDelegate hooks required by the Entrig SDK.
6
+ ///
7
+ /// **For Expo users**: The config plugin (`app.plugin.js`) injects these hooks
8
+ /// at prebuild time, so this file is not used.
9
+ ///
10
+ /// **For bare React Native users**: Call `EntrigAppDelegate.setup(launchOptions:)`
11
+ /// from your AppDelegate's `didFinishLaunchingWithOptions` and forward the
12
+ /// other delegate methods as shown below.
13
+ ///
14
+ /// ```swift
15
+ /// // AppDelegate.swift
16
+ /// import EntrigSDK
17
+ ///
18
+ /// @main
19
+ /// class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
20
+ /// func application(_ application: UIApplication,
21
+ /// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
22
+ /// UNUserNotificationCenter.current().delegate = self
23
+ /// EntrigAppDelegate.setup(launchOptions: launchOptions)
24
+ /// return true
25
+ /// }
26
+ ///
27
+ /// func application(_ application: UIApplication,
28
+ /// didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
29
+ /// Entrig.didRegisterForRemoteNotifications(deviceToken: deviceToken)
30
+ /// }
31
+ ///
32
+ /// func application(_ application: UIApplication,
33
+ /// didFailToRegisterForRemoteNotificationsWithError error: Error) {
34
+ /// Entrig.didFailToRegisterForRemoteNotifications(error: error)
35
+ /// }
36
+ ///
37
+ /// func userNotificationCenter(_ center: UNUserNotificationCenter,
38
+ /// willPresent notification: UNNotification,
39
+ /// withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
40
+ /// Entrig.willPresentNotification(notification)
41
+ /// completionHandler(Entrig.getPresentationOptions())
42
+ /// }
43
+ ///
44
+ /// func userNotificationCenter(_ center: UNUserNotificationCenter,
45
+ /// didReceive response: UNNotificationResponse,
46
+ /// withCompletionHandler completionHandler: @escaping () -> Void) {
47
+ /// Entrig.didReceiveNotification(response)
48
+ /// completionHandler()
49
+ /// }
50
+ /// }
51
+ /// ```
52
+ @objc public class EntrigAppDelegate: NSObject {
53
+
54
+ /// Call from `didFinishLaunchingWithOptions` to check for cold-start notifications.
55
+ @objc public static func setup(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
56
+ Entrig.checkLaunchNotification(launchOptions)
57
+ }
58
+ }
@@ -0,0 +1,24 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(Entrig, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(initialize:(NSDictionary *)config
7
+ withResolver:(RCTPromiseResolveBlock)resolve
8
+ withRejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(register:(NSString *)userId
11
+ withIsDebug:(NSNumber *)isDebug
12
+ withResolver:(RCTPromiseResolveBlock)resolve
13
+ withRejecter:(RCTPromiseRejectBlock)reject)
14
+
15
+ RCT_EXTERN_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve
16
+ withRejecter:(RCTPromiseRejectBlock)reject)
17
+
18
+ RCT_EXTERN_METHOD(unregister:(RCTPromiseResolveBlock)resolve
19
+ withRejecter:(RCTPromiseRejectBlock)reject)
20
+
21
+ RCT_EXTERN_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve
22
+ withRejecter:(RCTPromiseRejectBlock)reject)
23
+
24
+ @end
@@ -0,0 +1,138 @@
1
+ import React
2
+ import UserNotifications
3
+ import EntrigSDK
4
+
5
+ @objc(Entrig)
6
+ class EntrigModule: RCTEventEmitter {
7
+ private var hasListeners = false
8
+
9
+ override func supportedEvents() -> [String]! {
10
+ return ["onForegroundNotification", "onNotificationOpened"]
11
+ }
12
+
13
+ override class func requiresMainQueueSetup() -> Bool {
14
+ return true
15
+ }
16
+
17
+ override func startObserving() {
18
+ hasListeners = true
19
+ }
20
+
21
+ override func stopObserving() {
22
+ hasListeners = false
23
+ }
24
+
25
+ @objc(initialize:withResolver:withRejecter:)
26
+ func initializeSDK(config: [String: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
27
+ guard let apiKey = config["apiKey"] as? String, !apiKey.isEmpty else {
28
+ rejecter("INVALID_API_KEY", "API key is required and cannot be empty", nil)
29
+ return
30
+ }
31
+
32
+ let handlePermission = config["handlePermission"] as? Bool ?? true
33
+ let showForegroundNotification = config["showForegroundNotification"] as? Bool ?? true
34
+ let entrigConfig = EntrigConfig(apiKey: apiKey, handlePermission: handlePermission, showForegroundNotification: showForegroundNotification)
35
+
36
+ // Set up SDK listeners
37
+ Entrig.setOnForegroundNotificationListener(self)
38
+ Entrig.setOnNotificationOpenedListener(self)
39
+
40
+ Entrig.configure(config: entrigConfig) { success, error in
41
+ if success {
42
+ resolver(nil)
43
+ } else {
44
+ rejecter("INIT_ERROR", error ?? "Failed to initialize SDK", nil)
45
+ }
46
+ }
47
+ }
48
+
49
+ @objc(register:withIsDebug:withResolver:withRejecter:)
50
+ func register(userId: String, isDebugOverride: NSNumber?, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
51
+ let isDebug: Bool
52
+ if let override = isDebugOverride {
53
+ isDebug = override.boolValue
54
+ } else {
55
+ #if DEBUG
56
+ isDebug = true
57
+ #else
58
+ isDebug = false
59
+ #endif
60
+ }
61
+
62
+ Entrig.register(userId: userId, sdk: "react-native", isDebug: isDebug) { success, error in
63
+ if success {
64
+ resolver(nil)
65
+ } else {
66
+ rejecter("REGISTER_ERROR", error ?? "Registration failed", nil)
67
+ }
68
+ }
69
+ }
70
+
71
+ @objc(requestPermission:withRejecter:)
72
+ func requestPermission(resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
73
+ Entrig.requestPermission { granted, error in
74
+ if let error = error {
75
+ rejecter("PERMISSION_ERROR", error.localizedDescription, nil)
76
+ } else {
77
+ resolver(granted)
78
+ }
79
+ }
80
+ }
81
+
82
+ @objc(unregister:withRejecter:)
83
+ func unregister(resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
84
+ Entrig.unregister { success, error in
85
+ if success {
86
+ resolver(nil)
87
+ } else {
88
+ rejecter("UNREGISTER_ERROR", error ?? "Unregistration failed", nil)
89
+ }
90
+ }
91
+ }
92
+
93
+ @objc(getInitialNotification:withRejecter:)
94
+ func getInitialNotification(resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
95
+ if let event = Entrig.getInitialNotification() {
96
+ let payload: [String: Any] = [
97
+ "title": event.title ?? "",
98
+ "body": event.body ?? "",
99
+ "data": event.data ?? [:],
100
+ "isForeground": false
101
+ ]
102
+ resolver(payload)
103
+ } else {
104
+ resolver(nil)
105
+ }
106
+ }
107
+
108
+ // Helper method to send notification events to JavaScript
109
+ private func sendNotificationToJS(event: NotificationEvent, isForeground: Bool) {
110
+ guard hasListeners else { return }
111
+
112
+ let payload: [String: Any] = [
113
+ "title": event.title ?? "",
114
+ "body": event.body ?? "",
115
+ "data": event.data ?? [:],
116
+ "isForeground": isForeground
117
+ ]
118
+
119
+ if isForeground {
120
+ sendEvent(withName: "onForegroundNotification", body: payload)
121
+ } else {
122
+ sendEvent(withName: "onNotificationOpened", body: payload)
123
+ }
124
+ }
125
+ }
126
+
127
+ // MARK: - SDK Listeners
128
+ extension EntrigModule: OnNotificationReceivedListener {
129
+ func onNotificationReceived(_ event: NotificationEvent) {
130
+ sendNotificationToJS(event: event, isForeground: true)
131
+ }
132
+ }
133
+
134
+ extension EntrigModule: OnNotificationClickListener {
135
+ func onNotificationClick(_ event: NotificationEvent) {
136
+ sendNotificationToJS(event: event, isForeground: false)
137
+ }
138
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@entrig/react-native",
3
+ "version": "0.0.1",
4
+ "description": "Entrig | Push Notifications for Supabase",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "bin": {
8
+ "entrig-react-native": "./bin/setup.js"
9
+ },
10
+ "files": [
11
+ "build/",
12
+ "ios/",
13
+ "android/",
14
+ "plugin/",
15
+ "bin/",
16
+ "app.plugin.js",
17
+ "react-native.config.js",
18
+ "EntrigReactNative.podspec"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "clean": "rm -rf build/",
26
+ "lint": "eslint src/ --ext .ts,.tsx",
27
+ "test": "jest",
28
+ "prepare": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "react-native",
32
+ "expo",
33
+ "entrig",
34
+ "push-notifications",
35
+ "supabase",
36
+ "notifications",
37
+ "apns",
38
+ "fcm"
39
+ ],
40
+ "repository": "https://github.com/entrig/entrig-react-native",
41
+ "bugs": {
42
+ "url": "https://github.com/entrig/entrig-react-native/issues"
43
+ },
44
+ "author": "entrig <team@entrig.com> (https://github.com/entrig)",
45
+ "license": "MIT",
46
+ "homepage": "https://github.com/entrig/entrig-react-native#readme",
47
+ "devDependencies": {
48
+ "@expo/config-plugins": "^54.0.4",
49
+ "@types/react": "^19.0.0",
50
+ "typescript": "^5.0.0"
51
+ },
52
+ "peerDependencies": {
53
+ "@expo/config-plugins": ">=7.0.0",
54
+ "react": "*",
55
+ "react-native": "*"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@expo/config-plugins": {
59
+ "optional": true
60
+ },
61
+ "expo": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "expo": {
66
+ "autolinking": {
67
+ "nativeModulesDir": "."
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,298 @@
1
+ const {
2
+ withInfoPlist,
3
+ withEntitlementsPlist,
4
+ withDangerousMod,
5
+ } = require('@expo/config-plugins');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Expo config plugin for Entrig React Native SDK
11
+ * Automatically configures push notification entitlements, capabilities,
12
+ * and AppDelegate hooks.
13
+ *
14
+ * Add to app.json:
15
+ * {
16
+ * "expo": {
17
+ * "plugins": ["@entrig/react-native"]
18
+ * }
19
+ * }
20
+ */
21
+ function withEntrig(config) {
22
+ config = withInfoPlist(config, modifyInfoPlist);
23
+ config = withEntitlementsPlist(config, modifyEntitlementsPlist);
24
+ config = withDangerousMod(config, ['ios', modifyAppDelegate]);
25
+ return config;
26
+ }
27
+
28
+ /**
29
+ * Add UIBackgroundModes with remote-notification to Info.plist
30
+ */
31
+ function modifyInfoPlist(config) {
32
+ const infoPlist = config.modResults;
33
+
34
+ if (!infoPlist.UIBackgroundModes) {
35
+ infoPlist.UIBackgroundModes = [];
36
+ }
37
+
38
+ if (!infoPlist.UIBackgroundModes.includes('remote-notification')) {
39
+ infoPlist.UIBackgroundModes.push('remote-notification');
40
+ }
41
+
42
+ return config;
43
+ }
44
+
45
+ /**
46
+ * Add APS environment to entitlements
47
+ */
48
+ function modifyEntitlementsPlist(config) {
49
+ const entitlements = config.modResults;
50
+
51
+ if (!entitlements['aps-environment']) {
52
+ entitlements['aps-environment'] = 'development';
53
+ }
54
+
55
+ return config;
56
+ }
57
+
58
+ /**
59
+ * Inject AppDelegate hooks for push notification handling.
60
+ * Handles both Swift (modern Expo/RN 0.71+) and ObjC AppDelegates.
61
+ */
62
+ async function modifyAppDelegate(config) {
63
+ const iosRoot = path.join(config.modRequest.platformProjectRoot);
64
+
65
+ // Find the app delegate file
66
+ const appName = config.modRequest.projectName || config.name;
67
+ const swiftPath = path.join(iosRoot, appName, 'AppDelegate.swift');
68
+ const objcPath = path.join(iosRoot, appName, 'AppDelegate.mm');
69
+
70
+ if (fs.existsSync(swiftPath)) {
71
+ patchSwiftAppDelegate(swiftPath);
72
+ } else if (fs.existsSync(objcPath)) {
73
+ patchObjCAppDelegate(objcPath);
74
+ }
75
+
76
+ return config;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Swift AppDelegate patching (Expo prebuild generates Swift by default)
81
+ // ---------------------------------------------------------------------------
82
+
83
+ function patchSwiftAppDelegate(filePath) {
84
+ let content = fs.readFileSync(filePath, 'utf8');
85
+
86
+ // Already configured?
87
+ if (content.includes('Entrig.checkLaunchNotification') || content.includes('EntrigSDK')) {
88
+ return;
89
+ }
90
+
91
+ // 1. Add imports
92
+ if (!content.includes('import UserNotifications')) {
93
+ const importMatch = content.match(/^import \w+/m);
94
+ if (importMatch) {
95
+ content = content.replace(
96
+ importMatch[0],
97
+ importMatch[0] + '\nimport UserNotifications'
98
+ );
99
+ }
100
+ }
101
+
102
+ if (!content.includes('import EntrigSDK')) {
103
+ const importMatch = content.match(/^import \w+/m);
104
+ if (importMatch) {
105
+ content = content.replace(
106
+ importMatch[0],
107
+ importMatch[0] + '\nimport EntrigSDK'
108
+ );
109
+ }
110
+ }
111
+
112
+ // 2. Add UNUserNotificationCenterDelegate conformance to the class
113
+ if (!content.includes('UNUserNotificationCenterDelegate')) {
114
+ const classPattern = /(class\s+AppDelegate\s*:\s*[^{]+)\{/;
115
+ const classMatch = content.match(classPattern);
116
+ if (classMatch) {
117
+ const declaration = classMatch[1].trimEnd();
118
+ content = content.replace(
119
+ classMatch[0],
120
+ declaration + ', UNUserNotificationCenterDelegate {'
121
+ );
122
+ }
123
+ }
124
+
125
+ // 3. Add setup code inside didFinishLaunchingWithOptions
126
+ if (!content.includes('Entrig.checkLaunchNotification')) {
127
+ const didFinishPattern =
128
+ /func application\(\s*_\s+application:\s*UIApplication,\s*didFinishLaunchingWithOptions\s+launchOptions:[^)]*\)\s*->\s*Bool\s*\{/s;
129
+ const didFinishMatch = content.match(didFinishPattern);
130
+
131
+ if (didFinishMatch) {
132
+ const insertPos = didFinishMatch.index + didFinishMatch[0].length;
133
+ const setupCode = `
134
+ // Entrig: Setup push notification handling
135
+ UNUserNotificationCenter.current().delegate = self
136
+ Entrig.checkLaunchNotification(launchOptions)
137
+ `;
138
+ content = content.slice(0, insertPos) + setupCode + content.slice(insertPos);
139
+ }
140
+ }
141
+
142
+ // 4. Add delegate methods before the closing brace of the AppDelegate class
143
+ if (!content.includes('didRegisterForRemoteNotificationsWithDeviceToken')) {
144
+ const delegateMethods = `
145
+ // MARK: - Entrig Push Notification Handling
146
+
147
+ override func application(_ application: UIApplication,
148
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
149
+ Entrig.didRegisterForRemoteNotifications(deviceToken: deviceToken)
150
+ super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
151
+ }
152
+
153
+ override func application(_ application: UIApplication,
154
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
155
+ Entrig.didFailToRegisterForRemoteNotifications(error: error)
156
+ super.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
157
+ }
158
+
159
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
160
+ willPresent notification: UNNotification,
161
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
162
+ Entrig.willPresentNotification(notification)
163
+ completionHandler(Entrig.getPresentationOptions())
164
+ }
165
+
166
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
167
+ didReceive response: UNNotificationResponse,
168
+ withCompletionHandler completionHandler: @escaping () -> Void) {
169
+ Entrig.didReceiveNotification(response)
170
+ completionHandler()
171
+ }
172
+ `;
173
+
174
+ const lastBrace = findClassClosingBrace(content);
175
+ if (lastBrace !== -1) {
176
+ content = content.slice(0, lastBrace) + delegateMethods + content.slice(lastBrace);
177
+ }
178
+ }
179
+
180
+ fs.writeFileSync(filePath, content);
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // ObjC AppDelegate patching (older Expo/RN projects)
185
+ // ---------------------------------------------------------------------------
186
+
187
+ function patchObjCAppDelegate(filePath) {
188
+ let content = fs.readFileSync(filePath, 'utf8');
189
+
190
+ // Already configured?
191
+ if (content.includes('[Entrig checkLaunchNotification') || content.includes('EntrigSDK')) {
192
+ return;
193
+ }
194
+
195
+ // 1. Add imports
196
+ if (!content.includes('UserNotifications/UserNotifications.h')) {
197
+ content = '#import <UserNotifications/UserNotifications.h>\n' + content;
198
+ }
199
+
200
+ if (!content.includes('@import EntrigSDK')) {
201
+ content = '@import EntrigSDK;\n' + content;
202
+ }
203
+
204
+ // 2. Add UNUserNotificationCenterDelegate to interface
205
+ if (!content.includes('UNUserNotificationCenterDelegate')) {
206
+ const interfacePattern = /(@interface\s+AppDelegate[^>]*>)/;
207
+ const interfaceMatch = content.match(interfacePattern);
208
+ if (interfaceMatch) {
209
+ // Check if there's already a protocol list
210
+ if (interfaceMatch[0].includes('<')) {
211
+ // Add to existing protocol list before the closing >
212
+ content = content.replace(
213
+ interfaceMatch[0],
214
+ interfaceMatch[0].replace('>', ', UNUserNotificationCenterDelegate>')
215
+ );
216
+ }
217
+ }
218
+ }
219
+
220
+ // 3. Add setup in didFinishLaunchingWithOptions
221
+ if (!content.includes('[Entrig checkLaunchNotification')) {
222
+ const didFinishPattern =
223
+ /(-\s*\(BOOL\)application:.*?didFinishLaunchingWithOptions:.*?\{)/s;
224
+ const didFinishMatch = content.match(didFinishPattern);
225
+
226
+ if (didFinishMatch) {
227
+ const insertPos = didFinishMatch.index + didFinishMatch[0].length;
228
+ const setupCode = `
229
+ // Entrig: Setup push notification handling
230
+ [UNUserNotificationCenter currentNotificationCenter].delegate = self;
231
+ [Entrig checkLaunchNotification:launchOptions];
232
+ `;
233
+ content = content.slice(0, insertPos) + setupCode + content.slice(insertPos);
234
+ }
235
+ }
236
+
237
+ // 4. Add delegate methods before @end
238
+ if (!content.includes('didRegisterForRemoteNotificationsWithDeviceToken')) {
239
+ const endIdx = content.lastIndexOf('@end');
240
+ if (endIdx !== -1) {
241
+ const delegateMethods = `
242
+ // MARK: - Entrig Push Notification Handling
243
+
244
+ - (void)application:(UIApplication *)application
245
+ didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
246
+ [Entrig didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
247
+ }
248
+
249
+ - (void)application:(UIApplication *)application
250
+ didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
251
+ [Entrig didFailToRegisterForRemoteNotificationsWithError:error];
252
+ }
253
+
254
+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
255
+ willPresentNotification:(UNNotification *)notification
256
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
257
+ [Entrig willPresentNotification:notification];
258
+ completionHandler([Entrig getPresentationOptions]);
259
+ }
260
+
261
+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
262
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
263
+ withCompletionHandler:(void (^)(void))completionHandler {
264
+ [Entrig didReceiveNotification:response];
265
+ completionHandler();
266
+ }
267
+
268
+ `;
269
+ content = content.slice(0, endIdx) + delegateMethods + content.slice(endIdx);
270
+ }
271
+ }
272
+
273
+ fs.writeFileSync(filePath, content);
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Helpers
278
+ // ---------------------------------------------------------------------------
279
+
280
+ function findClassClosingBrace(content) {
281
+ const classStart = content.indexOf('class AppDelegate');
282
+ if (classStart === -1) return -1;
283
+
284
+ const openBrace = content.indexOf('{', classStart);
285
+ if (openBrace === -1) return -1;
286
+
287
+ let depth = 1;
288
+ let i = openBrace + 1;
289
+ while (i < content.length && depth > 0) {
290
+ if (content[i] === '{') depth++;
291
+ else if (content[i] === '}') depth--;
292
+ if (depth === 0) return i;
293
+ i++;
294
+ }
295
+ return -1;
296
+ }
297
+
298
+ module.exports = withEntrig;
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ ios: {
5
+ podspecPath: './EntrigReactNative.podspec',
6
+ },
7
+ android: {},
8
+ },
9
+ },
10
+ };