diff --git a/android/capacitor/src/main/java/com/getcapacitor/AppUUID.java b/android/capacitor/src/main/java/com/getcapacitor/AppUUID.java new file mode 100644 index 0000000000..3c1b1db605 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/AppUUID.java @@ -0,0 +1,65 @@ +package com.getcapacitor; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.appcompat.app.AppCompatActivity; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; +import java.util.UUID; + +public final class AppUUID { + + private static final String KEY = "CapacitorAppUUID"; + + public static String getAppUUID(AppCompatActivity activity) throws Exception { + assertAppUUID(activity); + return readUUID(activity); + } + + public static void regenerateAppUUID(AppCompatActivity activity) throws Exception { + try { + String uuid = generateUUID(); + writeUUID(activity, uuid); + } catch (NoSuchAlgorithmException ex) { + throw new Exception("Capacitor App UUID could not be generated."); + } + } + + private static void assertAppUUID(AppCompatActivity activity) throws Exception { + String uuid = readUUID(activity); + if (uuid.equals("")) { + regenerateAppUUID(activity); + } + } + + private static String generateUUID() throws NoSuchAlgorithmException { + MessageDigest salt = MessageDigest.getInstance("SHA-256"); + salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); + return bytesToHex(salt.digest()); + } + + private static String readUUID(AppCompatActivity activity) { + SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); + return sharedPref.getString(KEY, ""); + } + + private static void writeUUID(AppCompatActivity activity, String uuid) { + SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.putString(KEY, uuid); + editor.apply(); + } + + private static String bytesToHex(byte[] bytes) { + byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } +} diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index 979e61bad8..75fe641804 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0F83E885285A332E006C43CB /* AppUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F83E884285A332D006C43CB /* AppUUID.swift */; }; 2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */; }; 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */; }; 373A69C1255C9360000A6F44 /* NotificationHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */; }; @@ -138,6 +139,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0F83E884285A332D006C43CB /* AppUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUUID.swift; sourceTree = ""; }; 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.h"; sourceTree = ""; }; 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.m"; sourceTree = ""; }; 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandlerProtocol.swift; sourceTree = ""; }; @@ -355,6 +357,7 @@ 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */, 62E79C562638AF7500414164 /* assets */, A71289E527F380A500DADDF3 /* Router.swift */, + 0F83E884285A332D006C43CB /* AppUUID.swift */, ); path = Capacitor; sourceTree = ""; @@ -616,6 +619,7 @@ 62FABD1A25AE5C01007B3814 /* Array+Capacitor.swift in Sources */, 62959B172524DA7800A3D7F1 /* JSExport.swift in Sources */, 373A69C1255C9360000A6F44 /* NotificationHandlerProtocol.swift in Sources */, + 0F83E885285A332E006C43CB /* AppUUID.swift in Sources */, 625AF1ED258963C700869675 /* WebViewAssetHandler.swift in Sources */, 62959B3C2524DA7800A3D7F1 /* CAPBridgeDelegate.swift in Sources */, 62959B2F2524DA7800A3D7F1 /* DefaultPlugins.m in Sources */, diff --git a/ios/Capacitor/Capacitor/AppUUID.swift b/ios/Capacitor/Capacitor/AppUUID.swift new file mode 100644 index 0000000000..42ea92d6d8 --- /dev/null +++ b/ios/Capacitor/Capacitor/AppUUID.swift @@ -0,0 +1,53 @@ +import CommonCrypto +import Foundation + +private func hexString(_ iterator: Array.Iterator) -> String { + return iterator.map { String(format: "%02x", $0) }.joined() +} + +extension Data { + public var sha256: String { + var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + self.withUnsafeBytes { bytes in + _ = CC_SHA256(bytes.baseAddress, CC_LONG(self.count), &digest) + } + return hexString(digest.makeIterator()) + } +} + +public class AppUUID { + private static let key: String = "CapacitorAppUUID" + + public static func getAppUUID() -> String { + assertAppUUID() + return readUUID() + } + + public static func regenerateAppUUID() { + let uuid = generateUUID() + writeUUID(uuid) + } + + private static func assertAppUUID() { + let uuid = readUUID() + if uuid == "" { + regenerateAppUUID() + } + } + + private static func generateUUID() -> String { + let uuid: String = UUID.init().uuidString + return uuid.data(using: .utf8)!.sha256 + } + + private static func readUUID() -> String { + let defaults = UserDefaults.standard + return defaults.string(forKey: key) ?? "" + } + + private static func writeUUID(_ uuid: String) { + let defaults = UserDefaults.standard + defaults.set(uuid, forKey: key) + } + +}