diff --git a/CardSessionMobileSDK.podspec b/CardSessionMobileSDK.podspec new file mode 100644 index 0000000..2623791 --- /dev/null +++ b/CardSessionMobileSDK.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = 'CardSessionMobileSDK' + spec.version = '1.0.0' + spec.homepage = 'https://github.com/xendit/cards-session-mobile-sdk' + spec.source = { :http=> 'https://github.com/xendit/cards-session-mobile-sdk/releases/download/1.0.0/CardsSessionMobileSDK-1.0.0.zip'} + spec.author = { "mobile-sdk-team" => "mobile-sdk@xendit.co" } + spec.license = { :type => 'MIT', :text => ''} + spec.summary = 'Xendit\'s Cards Session SDK module' + spec.libraries = 'c++' + spec.platform = :ios, '14.0' + spec.ios.deployment_target = '14.0' + spec.vendored_frameworks = 'cardsSdk.xcframework' + spec.dependency 'XenditFingerprintSDK', '1.0.1' +end diff --git a/CardsSessionMobileSDKTarget/Dummy.swift b/CardsSessionMobileSDKTarget/Dummy.swift new file mode 100644 index 0000000..4dc64f6 --- /dev/null +++ b/CardsSessionMobileSDKTarget/Dummy.swift @@ -0,0 +1,2 @@ +//https://forums.swift.org/t/swiftpm-binary-target-with-sub-dependencies/40197/7 +//NOTE: Without commiting the dummy source file for the stub target to the repo with the manifest, the libSwiftPM just thrown an error about missing source files. \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..17306fb --- /dev/null +++ b/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version:5.9.0 +import PackageDescription + +let package = Package( + name: "CardsSessionMobileSDK", + platforms: [ + .iOS(.v14), + ], + products: [ + .library(name: "CardsSessionMobileSDK", targets: ["CardsSessionMobileSDKTarget"]) + ], + targets: [ + .binaryTarget( + name: "CardsSessionMobileSDK", + url: "https://github.com/xendit/cards-session-mobile-sdk/releases/download/1.0.0/CardsSessionMobileSDK-1.0.0.zip", + checksum: "ece9e7e73dffcdec868f7753a58544bb12d157ca4970b6816a9a3721f4e59827" + ), + .binaryTarget( + name: "XenditFingerprintSDK", + url: "https://cdn-xenshield.xendit.co/fingerprint-sdk/ios/1.0.1/XenditFingerprintSDK-1.0.1.zip", + checksum: "d8dbb2e00525eb7765972e10aa0cf49d990a7cc40ddff05d0f620a17b487ceb0" + ), + .target( + name: "CardsSessionMobileSDKTarget", + dependencies: [ + .target(name: "CardsSessionMobileSDK"), + .target(name: "XenditFingerprintSDK") + ], + path:"CardsSessionMobileSDKTarget", + linkerSettings: [ + .linkedFramework("SystemConfiguration"), + .linkedFramework("CoreTelephony", .when(platforms: [.iOS])) + ] + ) + ] +) diff --git a/README.md b/README.md index 398d64c..d458fb6 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,29 @@ dependencies { } ``` +### iOS - Cocoapod + +Add the following to your app's `Podfile`: + +```ruby +pod 'CardSessionMobileSDK', '~> 1.0.0' +``` + +**Important:** Import SDK in your project with CocoaPods integration, you can do as following + +```swift +import cardsSdk +``` +### iOS - Swift Package Manager + +In XCode go to `File` -> `Add Packages...` and provide URL to this repository in the search bar. Choose `Dependency Role` and select apropriate project. + +**Important:** Import SDK in your project with Swift Package Manager integration, you can do as following + +```swift +import cardsSdk +``` + ## Usage ### Initialize the SDK diff --git a/cardsSdk/PrivacyInfo.xcprivacy b/cardsSdk/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..dff5c17 --- /dev/null +++ b/cardsSdk/PrivacyInfo.xcprivacy @@ -0,0 +1,27 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + diff --git a/cardsSdk/build.gradle.kts b/cardsSdk/build.gradle.kts index 85cd97b..4574762 100644 --- a/cardsSdk/build.gradle.kts +++ b/cardsSdk/build.gradle.kts @@ -1,4 +1,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask +import java.nio.file.Files plugins { alias(libs.plugins.kotlin.multiplatform) @@ -12,7 +14,7 @@ kotlin { iosArm64() iosSimulatorArm64() - val xcframeworkName = "cardSdk" + val xcframeworkName = "cardsSdk" val xcFrameworkVersion = "1.0.0" val xcFrameworkBundleVersion = "1" // Increase it everytime version changed val xcf = XCFramework(xcframeworkName) @@ -36,9 +38,13 @@ kotlin { cocoapods { summary = "Cards Session SDK module" - homepage = "Link to the Cards Session Module homepage" - version = "1.0" + homepage = "https://github.com/xendit/cards-session-mobile-sdk" + version = "1.0.0" + license = "{ :type => 'MIT', :text => 'License text'}" ios.deploymentTarget = "14.0" + source = "{\n" + + " http: 'PUT THE URL Github Asset here'\n" + + " }" podfile = project.file("../iosApp/Podfile") framework { baseName = "cardsSdk" @@ -136,3 +142,52 @@ android { dependencies { coreLibraryDesugaring(libs.android.desugar) } + +interface Injected { + @get:Inject val fs: FileSystemOperations +} + +// Define a custom task type to handle the file copying +abstract class CopyPrivacyInfoTask : DefaultTask(), Injected { + @get:InputFiles + abstract val xcframeworkOutputs: ConfigurableFileCollection + + @get:InputFile + abstract val privacyFile: RegularFileProperty + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @TaskAction + fun copy() { + val xcframework = xcframeworkOutputs.first().toPath() + Files.find(xcframework, 2, { path, _ -> + val isFramework = path.fileName.toString().endsWith(".framework") + val destination = path.getName(path.count() - 2).fileName.toString() + val isIOS = destination.startsWith("ios-") + isFramework && isIOS + }).forEach { framework -> + fs.copy { + from(privacyFile) + into(framework) + } + } + } +} + +// Register the custom task +tasks.register("copyPrivacyInfoToFrameworks") { + val xcframeworkTask = tasks.named("podPublishReleaseXCFramework") + xcframeworkOutputs.from(xcframeworkTask.map { it.outputs.files }) + privacyFile.set(project.file("PrivacyInfo.xcprivacy")) + outputDir.set(layout.buildDirectory.dir("xcframework")) + + // Make this task run after XCFramework task + dependsOn(xcframeworkTask) +} + +// Make XCFramework task finalized by our copy task +tasks.named("podPublishReleaseXCFramework") { + finalizedBy("copyPrivacyInfoToFrameworks") +} + diff --git a/cardsSdk/cardsSdk.podspec b/cardsSdk/cardsSdk.podspec index 9050a33..1d43d5b 100644 --- a/cardsSdk/cardsSdk.podspec +++ b/cardsSdk/cardsSdk.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |spec| spec.name = 'cardsSdk' - spec.version = '1.0' - spec.homepage = 'Link to the Cards Session Module homepage' + spec.version = '1.0.0' + spec.homepage = 'https://github.com/xendit/cards-session-mobile-sdk' spec.source = { :http=> ''} spec.authors = '' - spec.license = '' + spec.license = { :type => 'MIT', :text => 'License text'} spec.summary = 'Cards Session SDK module' spec.vendored_frameworks = 'build/cocoapods/framework/cardsSdk.framework' spec.libraries = 'c++' diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 9f30637..1175c07 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - cardsSdk (1.0): + - cardsSdk (1.0.0): - XenditFingerprintSDK (= 1.0.1) - XenditFingerprintSDK (1.0.1) @@ -15,7 +15,7 @@ EXTERNAL SOURCES: :path: "../cardsSdk" SPEC CHECKSUMS: - cardsSdk: 9e0b4e91658434b731daf4d2262393018fe4f742 + cardsSdk: 6cf53581e3f04db427f8dd0700d1c11e78eac652 XenditFingerprintSDK: 29410a2dcf467fe33d6e0167f590deae47c753ee PODFILE CHECKSUM: 2d3ff3b1724835dab0bcce0b112c67a9f1440453 diff --git a/script/ios_step_1.sh b/script/ios_step_1.sh new file mode 100755 index 0000000..c931d82 --- /dev/null +++ b/script/ios_step_1.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +# Get the script directory and project root directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Configuration +GITHUB_REPO="xendit/cards-session-mobile-sdk" +FRAMEWORK_NAME="cardsSdk" +# Update path to be relative to project root +XCFRAMEWORK_PATH="${PROJECT_ROOT}/cardsSdk/build/cocoapods/publish/release/${FRAMEWORK_NAME}.xcframework" +RELEASE_DIR="${PROJECT_ROOT}/cardsSdk/build/cocoapods/publish/release" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration for Package.swift & .podspec updates +TARGET_NAME="CardsSessionMobileSDK" +PODSPEC_NAME="CardSessionMobileSDK.podspec" + + +# Function to format version tag to version number +format_version() { + local version_tag=$1 + echo "${version_tag#v}" +} + +# Function to get zip name +get_zip_name() { + local version_tag=$1 + local version_number=$(format_version "$version_tag") + echo "${TARGET_NAME}-${version_number}.zip" +} + +# Function to print step information +print_step() { + echo -e "\n${BLUE}=== $1 ===${NC}" +} + +# Function to check if command succeeded +check_status() { + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ $1 completed successfully${NC}" + else + echo -e "${RED}✗ $1 failed${NC}" + exit 1 + fi +} + +# Function to check if file exists +check_file_exists() { + if [ ! -d "$1" ]; then + echo -e "${RED}Error: $2 not found at: $1${NC}" + exit 1 + fi +} + +# Function to build XCFramework using Gradle +build_framework() { + print_step "Building XCFramework" + cd "$PROJECT_ROOT" + ./gradlew podPublishReleaseXCFramework + check_status "Gradle build" + check_file_exists "$XCFRAMEWORK_PATH" "XCFramework" +} + +# Function to create zip of XCFramework +create_zip() { + print_step "Creating zip file" + cd "$PROJECT_ROOT" + (cd "$(dirname "$XCFRAMEWORK_PATH")" && zip -r "$(pwd)/$ZIP_NAME" "$(basename "$XCFRAMEWORK_PATH")") + check_status "Zip creation" +} + +# Function to update Package.swift and podspec +update_package_files() { + local version_tag=$1 + local filename=$2 + + print_step "Updating Package.swift and podspec" + + # Calculate checksum + local checksum=$(sha256sum "${RELEASE_DIR}/${filename}" | cut -d ' ' -f 1) + + # Create temporary files + local tmp_swift=$(mktemp) + local tmp_podspec=$(mktemp) + local package_swift="${PROJECT_ROOT}/Package.swift" + local podspec_file="${PROJECT_ROOT}/${PODSPEC_NAME}" + + # Update Package.swift + awk -v target="$TARGET_NAME" -v tag="$version_tag" -v owner="${GITHUB_REPO%/*}" -v repo="${GITHUB_REPO#*/}" \ + -v checksum="$checksum" -v filename="$filename" ' + BEGIN { in_target = 0 } + { + if ($0 ~ "name: \"" target "\",$") { + in_target = 1 + print $0 + next + } + + if (in_target) { + if ($0 ~ /url:/) { + printf " url: \"https://github.com/%s/%s/releases/download/%s/%s\",\n", owner, repo, tag, filename + next + } + if ($0 ~ /checksum:/) { + printf " checksum: \"%s\"\n", checksum + in_target = 0 + next + } + } + print $0 + } + ' "$package_swift" > "$tmp_swift" + + # Update podspec + awk -v tag="$version_tag" -v owner="${GITHUB_REPO%/*}" -v repo="${GITHUB_REPO#*/}" -v filename="$filename" ' + { + if ($0 ~ /spec\.source.*=.*{.*:http/) { + printf " spec.source = { :http=> '\''https://github.com/%s/%s/releases/download/%s/%s'\''}\n", owner, repo, tag, filename + } else if ($0 ~ /spec\.version.*=.*/) { + printf " spec.version = '\''%s'\''\n", tag + } else { + print $0 + } + } + ' "$podspec_file" > "$tmp_podspec" + + # Replace original files + mv "$tmp_swift" "$package_swift" + mv "$tmp_podspec" "$podspec_file" + + check_status "Package files update" +} + +# Main execution +main() { + local version_tag=$1 + + if [ -z "$version_tag" ]; then + echo -e "${RED}Error: Version tag is required${NC}" + echo "Usage: $0 " + echo "Example: $0 v1.0.0" + exit 1 + fi + + ZIP_NAME=$(get_zip_name "$version_tag") + + # Check if required commands exist + if [ ! -f "${PROJECT_ROOT}/gradlew" ]; then + echo -e "${RED}Error: gradlew not found in project root directory${NC}" >&2 + exit 1 + fi + command -v jq >/dev/null 2>&1 || { echo -e "${RED}Error: jq is required but not installed${NC}" >&2; exit 1; } + + # Build and create zip + build_framework + create_zip + + # Update Package.swift and podspec + update_package_files "$version_tag" "$ZIP_NAME" + + echo -e "\n${GREEN}🎉 Process completed successfully!${NC}" + echo -e "${BLUE}Created zip file at: ${RELEASE_DIR}/${ZIP_NAME}${NC}" +} + +# Run the script with version tag argument +main "$1" \ No newline at end of file diff --git a/script/ios_step_2.sh b/script/ios_step_2.sh new file mode 100755 index 0000000..753a126 --- /dev/null +++ b/script/ios_step_2.sh @@ -0,0 +1,180 @@ +#!/bin/bash + + +# Configuration +GITHUB_REPO="xendit/cards-session-mobile-sdk" +GITHUB_TOKEN="your_github_token" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to check if release exists +check_release() { + local version_tag=$1 + + print_step "Checking if release $version_tag exists" + + response=$(curl -s \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$version_tag") + + if [ "$(echo "$response" | jq -r '.message')" = "Not Found" ]; then + echo -e "${BLUE}Release not found - will create new release${NC}" + return 1 + else + release_id=$(echo "$response" | jq -r '.id') + echo -e "${BLUE}Found existing release with ID: $release_id${NC}" + return 0 + fi +} + +# Function to print step information +print_step() { + echo -e "\n${BLUE}=== $1 ===${NC}" +} + +# Function to check if command succeeded +check_status() { + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ $1 completed successfully${NC}" + else + echo -e "${RED}✗ $1 failed${NC}" + exit 1 + fi +} + +# Function to get existing release upload URL +get_existing_release_url() { + local version_tag=$1 + + response=$(curl -s \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$version_tag") + + upload_url=$(echo "$response" | jq -r .upload_url | sed 's/{?name,label}//g') + echo "$upload_url" +} + +# Function to delete existing asset if it exists +delete_existing_asset() { + local version_tag=$1 + + print_step "Checking for existing assets" + + assets_response=$(curl -s \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$version_tag") + + asset_id=$(echo "$assets_response" | jq -r ".assets[] | select(.name == \"$ZIP_NAME\") | .id") + + if [ ! -z "$asset_id" ] && [ "$asset_id" != "null" ]; then + echo -e "${BLUE}Deleting existing asset with ID: $asset_id${NC}" + curl -s -X DELETE \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/assets/$asset_id" + check_status "Asset deletion" + fi +} + +# Function to create a new release +create_release() { + local version_tag=$1 + + print_step "Creating new release $version_tag" + + response=$(curl -s -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases" \ + -d "{ + \"tag_name\": \"$version_tag\", + \"name\": \"Release $version_tag\", + \"body\": \"Release $version_tag of $FRAMEWORK_NAME\", + \"draft\": false, + \"prerelease\": false + }") + + upload_url=$(echo "$response" | jq -r .upload_url | sed 's/{?name,label}//g') + + if [ -z "$upload_url" ] || [ "$upload_url" = "null" ]; then + echo -e "${RED}Error creating release. Response:${NC}" + echo "$response" + exit 1 + fi + + echo "$upload_url" +} + +# Function to upload asset to release +upload_asset() { + local upload_url=$1 + local zip_path="$ZIP_PATH" + + print_step "Uploading XCFramework" + + if [ ! -f "$zip_path" ]; then + echo -e "${RED}Error: Zip file not found: $zip_path${NC}" + exit 1 + fi + + echo -e "${BLUE}Uploading file: $zip_path${NC}" + echo -e "${BLUE}File size: $(ls -lh "$zip_path" | awk '{print $5}')${NC}" + + cd "$(dirname "$zip_path")" + + curl -v \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/zip" \ + -H "Accept: application/vnd.github.v3+json" \ + --data-binary "@${ZIP_NAME}" \ + "${upload_url}?name=${ZIP_NAME}" 2>&1 + + check_status "Release upload" +} + +# Main execution +main() { + local version_tag=$1 + local zip_path=$2 + local upload_url + + if [ -z "$version_tag" ] || [ -z "$zip_path" ]; then + echo -e "${RED}Error: Version tag and zip path are required${NC}" + echo "Usage: $0 " + echo "Example: $0 v1.0.0 /path/to/framework.zip" + exit 1 + fi + + ZIP_NAME=$(basename "$zip_path") + ZIP_PATH="$zip_path" + + # Handle GitHub release + if check_release "$version_tag"; then + echo -e "${BLUE}Using existing release...${NC}" + delete_existing_asset "$version_tag" + upload_url=$(get_existing_release_url "$version_tag") + else + echo -e "${BLUE}Creating new release...${NC}" + upload_url=$(create_release "$version_tag") + fi + + # Upload to GitHub + upload_asset "$upload_url" + + # Cleanup after successful upload + print_step "Cleaning up" + rm "$ZIP_PATH" 2>/dev/null + check_status "Cleanup" + + echo -e "\n${GREEN}🎉 Release upload completed successfully!${NC}" +} + +# Run the script with version tag and zip path arguments +main "$1" "$2" \ No newline at end of file diff --git a/script/ios_step_3.sh b/script/ios_step_3.sh new file mode 100755 index 0000000..48c9e7d --- /dev/null +++ b/script/ios_step_3.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Get the script directory and project root directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Configuration +PODSPEC_NAME="CardSessionMobileSDK.podspec" +PODSPEC_PATH="${PROJECT_ROOT}/${PODSPEC_NAME}" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print step information +print_step() { + echo -e "\n${BLUE}=== $1 ===${NC}" +} + +# Function to check if command succeeded +check_status() { + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ $1 completed successfully${NC}" + else + echo -e "${RED}✗ $1 failed${NC}" + exit 1 + fi +} + +# Function to check if pod is installed +check_pod_installation() { + if ! command -v pod &> /dev/null; then + echo -e "${RED}Error: CocoaPods is not installed${NC}" + echo "Please install CocoaPods using: gem install cocoapods" + exit 1 + fi +} + +# Function to validate podspec +validate_podspec() { + print_step "Validating podspec" + + if [ ! -f "$PODSPEC_PATH" ]; then + echo -e "${RED}Error: Podspec not found at: $PODSPEC_PATH${NC}" + exit 1 + fi + + pod spec lint "$PODSPEC_PATH" --allow-warnings + check_status "Podspec validation" +} + +# Function to push to trunk +push_to_trunk() { + print_step "Pushing to CocoaPods Trunk" + + # Adding --verbose flag for detailed output + pod trunk push "$PODSPEC_PATH" --allow-warnings --verbose + check_status "Pod trunk push" +} + +# Main execution +main() { + # Check if cocoapods is installed + check_pod_installation + + # First validate the podspec + validate_podspec + + # Then push to trunk + push_to_trunk + + echo -e "\n${GREEN}🎉 Successfully published podspec to CocoaPods Trunk!${NC}" +} + +# Run the script +main \ No newline at end of file