@poilabs-dev/analysis-sdk-plugin
Version:
Official Expo Config Plugin for integrating the Poilabs Analysis SDK
376 lines (316 loc) • 12.5 kB
JavaScript
const { withInfoPlist, withDangerousMod } = require("@expo/config-plugins");
const fs = require("fs");
const path = require("path");
function withPoilabsInfoPlist(config) {
return withInfoPlist(config, (mod) => {
const plist = mod.modResults;
plist.NSLocationWhenInUseUsageDescription =
"We use your location to detect nearby beacons.";
plist.NSLocationAlwaysUsageDescription =
"We use your location even in the background for beacon scanning.";
plist.NSLocationAlwaysAndWhenInUseUsageDescription =
"We use your location to detect nearby beacons even in the background.";
plist.NSBluetoothAlwaysUsageDescription =
"Bluetooth is required to scan beacons nearby.";
plist.NSBluetoothPeripheralUsageDescription =
"Bluetooth is required to scan beacons nearby.";
return mod;
});
}
function withPoilabsBackgroundModes(config) {
return withInfoPlist(config, (mod) => {
const plist = mod.modResults;
const UIBackgroundModes = plist.UIBackgroundModes || [];
if (!UIBackgroundModes.includes("bluetooth-central")) {
UIBackgroundModes.push("bluetooth-central");
}
if (!UIBackgroundModes.includes("location")) {
UIBackgroundModes.push("location");
}
plist.UIBackgroundModes = UIBackgroundModes;
return mod;
});
}
function withPoilabsPodfile(config) {
return withDangerousMod(config, [
"ios",
async (modConfig) => {
const podfile = path.join(
modConfig.modRequest.projectRoot,
"ios/Podfile"
);
if (fs.existsSync(podfile)) {
let podText = fs.readFileSync(podfile, "utf8");
if (!podText.includes("use_frameworks! :linkage => :static")) {
podText = podText.replace(
/platform :ios[^\n]*\n/,
(match) => `${match}use_frameworks! :linkage => :static\n\n`
);
}
if (!podText.includes("pod 'PoilabsAnalysis'")) {
podText = podText.replace(
/target ['"][^'"]+['"] do/,
(m) => `${m}\n pod 'PoilabsAnalysis'`
);
}
fs.writeFileSync(podfile, podText);
}
return modConfig;
},
]);
}
function withPoilabsNativeModules(config) {
return withDangerousMod(config, [
"ios",
async (modConfig) => {
const root = modConfig.modRequest.projectRoot;
const projectName = modConfig.modRequest.projectName;
const appDelegateSwiftFile = path.join(
root,
"ios",
projectName,
"AppDelegate.swift"
);
const appDelegateMMFile = path.join(
root,
"ios",
projectName,
"AppDelegate.mm"
);
const appDelegateMFile = path.join(
root,
"ios",
projectName,
"AppDelegate.m"
);
const isSwiftProject = fs.existsSync(appDelegateSwiftFile);
const appDelegateFile = isSwiftProject
? appDelegateSwiftFile
: fs.existsSync(appDelegateMMFile)
? appDelegateMMFile
: fs.existsSync(appDelegateMFile)
? appDelegateMFile
: null;
const moduleDir = path.join(root, "ios", projectName, "PoilabsModule");
if (!fs.existsSync(moduleDir)) {
fs.mkdirSync(moduleDir, { recursive: true });
}
if (isSwiftProject) {
if (appDelegateFile && fs.existsSync(appDelegateFile)) {
let appDelegate = fs.readFileSync(appDelegateFile, "utf8");
if (!appDelegate.includes("import PoilabsAnalysis")) {
const importMatch = appDelegate.match(/^import.*$/gm);
if (importMatch && importMatch.length > 0) {
const lastImport = importMatch[importMatch.length - 1];
appDelegate = appDelegate.replace(
lastImport,
`${lastImport}\nimport PoilabsAnalysis`
);
}
}
if (!appDelegate.includes("PLSuspendedAnalysisManager")) {
const returnRegex =
/return\s+super\.application\s*\(\s*application\s*,\s*didFinishLaunchingWithOptions\s*:\s*launchOptions\s*\)/;
if (returnRegex.test(appDelegate)) {
const poilabsCode = `
if let options = launchOptions, options[UIApplication.LaunchOptionsKey.location] != nil {
if application.applicationState == .background {
PLSuspendedAnalysisManager.sharedInstance()?.startBeaconMonitoring()
}
}
`;
appDelegate = appDelegate.replace(
returnRegex,
`${poilabsCode}return super.application(application, didFinishLaunchingWithOptions: launchOptions)`
);
} else {
const appDelegatePattern =
/public\s+override\s+func\s+application\s*\(\s*_\s+application\s*:\s*UIApplication\s*,\s*didFinishLaunchingWithOptions\s+launchOptions\s*:.*?\)\s*->\s*Bool\s*\{/s;
if (appDelegatePattern.test(appDelegate)) {
const match = appDelegatePattern.exec(appDelegate);
if (match) {
const methodStart = match.index + match[0].length;
let openBraces = 1;
let closeBraces = 0;
let endIndex = methodStart;
for (let i = methodStart; i < appDelegate.length; i++) {
if (appDelegate[i] === "{") openBraces++;
if (appDelegate[i] === "}") closeBraces++;
if (openBraces === closeBraces + 1) {
endIndex = i;
break;
}
}
if (endIndex > methodStart) {
const poilabsCode = `
if let options = launchOptions, options[UIApplication.LaunchOptionsKey.location] != nil {
if application.applicationState == .background {
PLSuspendedAnalysisManager.sharedInstance()?.startBeaconMonitoring()
}
}
`;
appDelegate =
appDelegate.substring(0, endIndex) +
poilabsCode +
appDelegate.substring(endIndex);
}
}
}
}
}
fs.writeFileSync(appDelegateFile, appDelegate);
}
const swiftModuleContent = `
import Foundation
import PoilabsAnalysis
import React
@objc(PoilabsAnalysisModule)
class PoilabsAnalysisModule: NSObject, PLAnalysisManagerDelegate {
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func startPoilabsAnalysis(_ applicationId: String, applicationSecret: String, uniqueIdentifier: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) -> Void {
PLAnalysisSettings.sharedInstance().applicationId = applicationId
PLAnalysisSettings.sharedInstance().applicationSecret = applicationSecret
PLAnalysisSettings.sharedInstance().analysisUniqueIdentifier = uniqueIdentifier
PLConfigManager.sharedInstance().getReadyForTracking { [weak self] error in
if error != nil {
print("Poilabs Error: \\(error!)")
resolver(false)
} else {
print("Poilabs SDK initialized successfully")
PLSuspendedAnalysisManager.sharedInstance()?.stopBeaconMonitoring()
PLStandardAnalysisManager.sharedInstance()?.startBeaconMonitoring()
PLStandardAnalysisManager.sharedInstance()?.delegate = self
resolver(true)
}
}
}
@objc
func stopPoilabsAnalysis(_ resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) -> Void {
PLAnalysisSettings.sharedInstance()?.closeAllActions()
resolver(true)
}
@objc
func updateUniqueId(_ uniqueId: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) -> Void {
PLAnalysisSettings.sharedInstance()?.analysisUniqueIdentifier = uniqueId
resolver(true)
}
}
`;
const objcModuleContent = `
#import <React/RCTBridgeModule.h>
RCT_EXTERN_MODULE(PoilabsAnalysisModule, NSObject)
RCT_EXTERN_METHOD(startPoilabsAnalysis:(NSString *)applicationId
applicationSecret:(NSString *)applicationSecret
uniqueIdentifier:(NSString *)uniqueIdentifier
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(stopPoilabsAnalysis:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(updateUniqueId:(NSString *)uniqueId
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
`;
fs.writeFileSync(
path.join(moduleDir, "PoilabsAnalysisModule.swift"),
swiftModuleContent
);
fs.writeFileSync(
path.join(moduleDir, "PoilabsAnalysisModule.m"),
objcModuleContent
);
const bridgingHeaderPath = path.join(
root,
"ios",
projectName,
`${projectName}-Bridging-Header.h`
);
let bridgingHeaderContent = "";
if (fs.existsSync(bridgingHeaderPath)) {
bridgingHeaderContent = fs.readFileSync(bridgingHeaderPath, "utf8");
}
if (
!bridgingHeaderContent.includes(
"#import <PoilabsAnalysis/PoilabsAnalysis.h>"
)
) {
bridgingHeaderContent +=
"\n#import <PoilabsAnalysis/PoilabsAnalysis.h>\n";
}
if (
!bridgingHeaderContent.includes("#import <React/RCTBridgeModule.h>")
) {
bridgingHeaderContent += "\n#import <React/RCTBridgeModule.h>\n";
}
if (!bridgingHeaderContent.includes("#import <React/RCTUtils.h>")) {
bridgingHeaderContent += "\n#import <React/RCTUtils.h>\n";
}
fs.writeFileSync(bridgingHeaderPath, bridgingHeaderContent);
} else {
if (appDelegateFile && fs.existsSync(appDelegateFile)) {
let appDelegate = fs.readFileSync(appDelegateFile, "utf8");
if (
!appDelegate.includes("#import <PoilabsAnalysis/PoilabsAnalysis.h>")
) {
appDelegate = appDelegate.replace(
/#import "AppDelegate.h"/,
`#import "AppDelegate.h"\n#import <PoilabsAnalysis/PoilabsAnalysis.h>`
);
}
if (!appDelegate.includes("PLSuspendedAnalysisManager")) {
appDelegate = appDelegate.replace(
/- \(BOOL\)application:\(UIApplication \*\)application didFinishLaunchingWithOptions:\(NSDictionary \*\)launchOptions\s*{/,
`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
if (application.applicationState == UIApplicationStateBackground) {
[[PLSuspendedAnalysisManager sharedInstance] startBeaconMonitoring];
}
}`
);
}
fs.writeFileSync(appDelegateFile, appDelegate);
}
const sourceDir = path.join(
root,
"node_modules/@poilabs-dev/analysis-sdk-plugin/src/ios"
);
const moduleFiles = [
"PoilabsAnalysisModule.h",
"PoilabsAnalysisModule.m",
];
moduleFiles.forEach((file) => {
const sourcePath = path.join(sourceDir, file);
const destPath = path.join(moduleDir, file);
if (fs.existsSync(sourcePath)) {
const content = fs.readFileSync(sourcePath, "utf8");
fs.writeFileSync(destPath, content, "utf8");
} else {
console.warn(`Kaynak dosya bulunamadı: ${sourcePath}`);
}
});
}
return modConfig;
},
]);
}
function withPoilabsXcodeProject(config) {
return withDangerousMod(config, [
"ios",
async (modConfig) => {
return modConfig;
},
]);
}
function withPoilabsIOS(config) {
config = withPoilabsInfoPlist(config);
config = withPoilabsBackgroundModes(config);
config = withPoilabsPodfile(config);
config = withPoilabsNativeModules(config);
config = withPoilabsXcodeProject(config);
return config;
}
module.exports = withPoilabsIOS;