diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index d04c6dc1..c6d63955 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -7,6 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 961058DF2C355B5500E1F1D8 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961058DE2C355B5500E1F1D8 /* NotificationService.swift */; }; + 961058E32C355B5500E1F1D8 /* BitkitNotification.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 961058DC2C355B5500E1F1D8 /* BitkitNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 961058E82C35791700E1F1D8 /* LightningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B12A022C2EC65000DD07B0 /* LightningService.swift */; }; + 961058E92C35792100E1F1D8 /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D22C32CE79004A92FC /* Env.swift */; }; + 961058EA2C35793000E1F1D8 /* LnPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6DE2C32ED7B004A92FC /* LnPeer.swift */; }; + 961058EB2C35793000E1F1D8 /* WalletNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6DC2C32EAA8004A92FC /* WalletNetwork.swift */; }; + 961058EE2C35798C00E1F1D8 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = 961058ED2C35798C00E1F1D8 /* LDKNode */; }; + 961058F02C35799400E1F1D8 /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = 961058EF2C35799400E1F1D8 /* BitcoinDevKit */; }; 9637E6D32C32CE79004A92FC /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D22C32CE79004A92FC /* Env.swift */; }; 9637E6D52C32D811004A92FC /* OnChainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D42C32D811004A92FC /* OnChainService.swift */; }; 9637E6D82C32D8A7004A92FC /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9637E6D72C32D8A7004A92FC /* BitcoinDevKit */; }; @@ -16,6 +24,10 @@ 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = 96B129FC2C2EC05D00DD07B0 /* LDKNode */; }; 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */; }; 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B12A022C2EC65000DD07B0 /* LightningService.swift */; }; + 96F261322C369C2100167439 /* ServiceQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F261312C369C2100167439 /* ServiceQueue.swift */; }; + 96F261332C369C2100167439 /* ServiceQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F261312C369C2100167439 /* ServiceQueue.swift */; }; + 96F261362C369D2400167439 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F261352C369D2400167439 /* Errors.swift */; }; + 96F261372C369D2400167439 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F261352C369D2400167439 /* Errors.swift */; }; 96FE1F652C2DE6AA006D0C8B /* BitkitApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */; }; 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */; }; 96FE1F692C2DE6AC006D0C8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */; }; @@ -23,9 +35,19 @@ 96FE1F772C2DE6AC006D0C8B /* BitkitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F762C2DE6AC006D0C8B /* BitkitTests.swift */; }; 96FE1F812C2DE6AC006D0C8B /* BitkitUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F802C2DE6AC006D0C8B /* BitkitUITests.swift */; }; 96FE1F832C2DE6AC006D0C8B /* BitkitUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F822C2DE6AC006D0C8B /* BitkitUITestsLaunchTests.swift */; }; + 96FE5A192C46594500860ADC /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE5A182C46594500860ADC /* LogView.swift */; }; + 96FE5A1B2C46A4DD00860ADC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE5A1A2C46A4DD00860ADC /* Logger.swift */; }; + 96FE5A1C2C46A4E100860ADC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE5A1A2C46A4DD00860ADC /* Logger.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 961058E12C355B5500E1F1D8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 96FE1F592C2DE6AA006D0C8B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 961058DB2C355B5500E1F1D8; + remoteInfo = BitkitNotification; + }; 96FE1F732C2DE6AC006D0C8B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 96FE1F592C2DE6AA006D0C8B /* Project object */; @@ -42,7 +64,24 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 961058E72C355B5500E1F1D8 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 961058E32C355B5500E1F1D8 /* BitkitNotification.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 961058DC2C355B5500E1F1D8 /* BitkitNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BitkitNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 961058DE2C355B5500E1F1D8 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 961058E02C355B5500E1F1D8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9637E6D22C32CE79004A92FC /* Env.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; }; 9637E6D42C32D811004A92FC /* OnChainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainService.swift; sourceTree = ""; }; 9637E6D92C32E573004A92FC /* OnChainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainViewModel.swift; sourceTree = ""; }; @@ -50,6 +89,9 @@ 9637E6DE2C32ED7B004A92FC /* LnPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LnPeer.swift; sourceTree = ""; }; 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningViewModel.swift; sourceTree = ""; }; 96B12A022C2EC65000DD07B0 /* LightningService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningService.swift; sourceTree = ""; }; + 96C246C32C455AF60058222C /* BitkitNotification.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BitkitNotification.entitlements; sourceTree = ""; }; + 96F261312C369C2100167439 /* ServiceQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceQueue.swift; sourceTree = ""; }; + 96F261352C369D2400167439 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bitkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitkitApp.swift; sourceTree = ""; }; 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -61,9 +103,20 @@ 96FE1F7C2C2DE6AC006D0C8B /* BitkitUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitkitUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 96FE1F802C2DE6AC006D0C8B /* BitkitUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitkitUITests.swift; sourceTree = ""; }; 96FE1F822C2DE6AC006D0C8B /* BitkitUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitkitUITestsLaunchTests.swift; sourceTree = ""; }; + 96FE5A182C46594500860ADC /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = ""; }; + 96FE5A1A2C46A4DD00860ADC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 961058D92C355B5500E1F1D8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 961058F02C35799400E1F1D8 /* BitcoinDevKit in Frameworks */, + 961058EE2C35798C00E1F1D8 /* LDKNode in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 96FE1F5E2C2DE6AA006D0C8B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -90,6 +143,30 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 961058DD2C355B5500E1F1D8 /* BitkitNotification */ = { + isa = PBXGroup; + children = ( + 96C246C32C455AF60058222C /* BitkitNotification.entitlements */, + 961058DE2C355B5500E1F1D8 /* NotificationService.swift */, + 961058E02C355B5500E1F1D8 /* Info.plist */, + ); + path = BitkitNotification; + sourceTree = ""; + }; + 961058EC2C35798C00E1F1D8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 961058F12C35904F00E1F1D8 /* Extensions */ = { + isa = PBXGroup; + children = ( + ); + path = Extensions; + sourceTree = ""; + }; 9637E6D12C32CE65004A92FC /* Constants */ = { isa = PBXGroup; children = ( @@ -121,17 +198,29 @@ children = ( 96B12A022C2EC65000DD07B0 /* LightningService.swift */, 9637E6D42C32D811004A92FC /* OnChainService.swift */, + 96F261312C369C2100167439 /* ServiceQueue.swift */, ); path = Services; sourceTree = ""; }; + 96F261342C369D1300167439 /* Utilities */ = { + isa = PBXGroup; + children = ( + 96F261352C369D2400167439 /* Errors.swift */, + 96FE5A1A2C46A4DD00860ADC /* Logger.swift */, + ); + path = Utilities; + sourceTree = ""; + }; 96FE1F582C2DE6AA006D0C8B = { isa = PBXGroup; children = ( 96FE1F632C2DE6AA006D0C8B /* Bitkit */, 96FE1F752C2DE6AC006D0C8B /* BitkitTests */, 96FE1F7F2C2DE6AC006D0C8B /* BitkitUITests */, + 961058DD2C355B5500E1F1D8 /* BitkitNotification */, 96FE1F622C2DE6AA006D0C8B /* Products */, + 961058EC2C35798C00E1F1D8 /* Frameworks */, ); sourceTree = ""; }; @@ -141,6 +230,7 @@ 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */, 96FE1F722C2DE6AC006D0C8B /* BitkitTests.xctest */, 96FE1F7C2C2DE6AC006D0C8B /* BitkitUITests.xctest */, + 961058DC2C355B5500E1F1D8 /* BitkitNotification.appex */, ); name = Products; sourceTree = ""; @@ -150,10 +240,13 @@ children = ( 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */, 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */, + 96FE5A172C46592800860ADC /* Views */, 96B129FE2C2EC0ED00DD07B0 /* ViewModels */, 96B12A012C2EC61500DD07B0 /* Services */, 9637E6DB2C32EA84004A92FC /* Models */, 9637E6D12C32CE65004A92FC /* Constants */, + 961058F12C35904F00E1F1D8 /* Extensions */, + 96F261342C369D1300167439 /* Utilities */, 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */, 96FE1F6A2C2DE6AC006D0C8B /* Bitkit.entitlements */, 96FE1F6B2C2DE6AC006D0C8B /* Preview Content */, @@ -186,9 +279,38 @@ path = BitkitUITests; sourceTree = ""; }; + 96FE5A172C46592800860ADC /* Views */ = { + isa = PBXGroup; + children = ( + 96FE5A182C46594500860ADC /* LogView.swift */, + ); + path = Views; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 961058DB2C355B5500E1F1D8 /* BitkitNotification */ = { + isa = PBXNativeTarget; + buildConfigurationList = 961058E42C355B5500E1F1D8 /* Build configuration list for PBXNativeTarget "BitkitNotification" */; + buildPhases = ( + 961058D82C355B5500E1F1D8 /* Sources */, + 961058D92C355B5500E1F1D8 /* Frameworks */, + 961058DA2C355B5500E1F1D8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitkitNotification; + packageProductDependencies = ( + 961058ED2C35798C00E1F1D8 /* LDKNode */, + 961058EF2C35799400E1F1D8 /* BitcoinDevKit */, + ); + productName = BitkitNotification; + productReference = 961058DC2C355B5500E1F1D8 /* BitkitNotification.appex */; + productType = "com.apple.product-type.app-extension"; + }; 96FE1F602C2DE6AA006D0C8B /* Bitkit */ = { isa = PBXNativeTarget; buildConfigurationList = 96FE1F862C2DE6AC006D0C8B /* Build configuration list for PBXNativeTarget "Bitkit" */; @@ -196,10 +318,12 @@ 96FE1F5D2C2DE6AA006D0C8B /* Sources */, 96FE1F5E2C2DE6AA006D0C8B /* Frameworks */, 96FE1F5F2C2DE6AA006D0C8B /* Resources */, + 961058E72C355B5500E1F1D8 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 961058E22C355B5500E1F1D8 /* PBXTargetDependency */, ); name = Bitkit; packageProductDependencies = ( @@ -256,6 +380,9 @@ LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { + 961058DB2C355B5500E1F1D8 = { + CreatedOnToolsVersion = 15.4; + }; 96FE1F602C2DE6AA006D0C8B = { CreatedOnToolsVersion = 15.4; }; @@ -289,11 +416,19 @@ 96FE1F602C2DE6AA006D0C8B /* Bitkit */, 96FE1F712C2DE6AC006D0C8B /* BitkitTests */, 96FE1F7B2C2DE6AC006D0C8B /* BitkitUITests */, + 961058DB2C355B5500E1F1D8 /* BitkitNotification */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 961058DA2C355B5500E1F1D8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 96FE1F5F2C2DE6AA006D0C8B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -320,16 +455,35 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 961058D82C355B5500E1F1D8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 961058E92C35792100E1F1D8 /* Env.swift in Sources */, + 961058E82C35791700E1F1D8 /* LightningService.swift in Sources */, + 961058EA2C35793000E1F1D8 /* LnPeer.swift in Sources */, + 96F261332C369C2100167439 /* ServiceQueue.swift in Sources */, + 961058EB2C35793000E1F1D8 /* WalletNetwork.swift in Sources */, + 961058DF2C355B5500E1F1D8 /* NotificationService.swift in Sources */, + 96F261372C369D2400167439 /* Errors.swift in Sources */, + 96FE5A1C2C46A4E100860ADC /* Logger.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 96FE1F5D2C2DE6AA006D0C8B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9637E6D32C32CE79004A92FC /* Env.swift in Sources */, + 96FE5A192C46594500860ADC /* LogView.swift in Sources */, 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */, 9637E6DD2C32EAA8004A92FC /* WalletNetwork.swift in Sources */, + 96F261362C369D2400167439 /* Errors.swift in Sources */, 9637E6DA2C32E573004A92FC /* OnChainViewModel.swift in Sources */, + 96F261322C369C2100167439 /* ServiceQueue.swift in Sources */, 9637E6DF2C32ED7B004A92FC /* LnPeer.swift in Sources */, 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */, + 96FE5A1B2C46A4DD00860ADC /* Logger.swift in Sources */, 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */, 9637E6D52C32D811004A92FC /* OnChainService.swift in Sources */, 96FE1F652C2DE6AA006D0C8B /* BitkitApp.swift in Sources */, @@ -356,6 +510,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 961058E22C355B5500E1F1D8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 961058DB2C355B5500E1F1D8 /* BitkitNotification */; + targetProxy = 961058E12C355B5500E1F1D8 /* PBXContainerItemProxy */; + }; 96FE1F742C2DE6AC006D0C8B /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 96FE1F602C2DE6AA006D0C8B /* Bitkit */; @@ -369,6 +528,63 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 961058E52C355B5500E1F1D8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KYH47R284B; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BitkitNotification/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BitkitNotification; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = to.Bitkit.native.BitkitNotification; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 961058E62C355B5500E1F1D8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KYH47R284B; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BitkitNotification/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BitkitNotification; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = to.Bitkit.native.BitkitNotification; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 96FE1F842C2DE6AC006D0C8B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -486,6 +702,7 @@ 96FE1F872C2DE6AC006D0C8B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements; @@ -526,6 +743,7 @@ 96FE1F882C2DE6AC006D0C8B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements; @@ -656,6 +874,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 961058E42C355B5500E1F1D8 /* Build configuration list for PBXNativeTarget "BitkitNotification" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 961058E52C355B5500E1F1D8 /* Debug */, + 961058E62C355B5500E1F1D8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 96FE1F5C2C2DE6AA006D0C8B /* Build configuration list for PBXProject "Bitkit" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -714,6 +941,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 961058ED2C35798C00E1F1D8 /* LDKNode */ = { + isa = XCSwiftPackageProductDependency; + package = 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */; + productName = LDKNode; + }; + 961058EF2C35799400E1F1D8 /* BitcoinDevKit */ = { + isa = XCSwiftPackageProductDependency; + package = 9637E6D62C32D8A7004A92FC /* XCRemoteSwiftPackageReference "bdk-swift" */; + productName = BitcoinDevKit; + }; 9637E6D72C32D8A7004A92FC /* BitcoinDevKit */ = { isa = XCSwiftPackageProductDependency; package = 9637E6D62C32D8A7004A92FC /* XCRemoteSwiftPackageReference "bdk-swift" */; diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate b/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate index 9a82fa0f..cbdf6c87 100644 Binary files a/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate and b/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Bitkit.xcodeproj/xcshareddata/xcschemes/Bitkit.xcscheme b/Bitkit.xcodeproj/xcshareddata/xcschemes/Bitkit.xcscheme new file mode 100644 index 00000000..b10a30f7 --- /dev/null +++ b/Bitkit.xcodeproj/xcshareddata/xcschemes/Bitkit.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bitkit.xcodeproj/xcshareddata/xcschemes/BitkitNotification.xcscheme b/Bitkit.xcodeproj/xcshareddata/xcschemes/BitkitNotification.xcscheme new file mode 100644 index 00000000..78d79169 --- /dev/null +++ b/Bitkit.xcodeproj/xcshareddata/xcschemes/BitkitNotification.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..264ca11a --- /dev/null +++ b/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcschemes/xcschememanagement.plist b/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcschemes/xcschememanagement.plist index f79ff96e..1f42bbf1 100644 --- a/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Bitkit.xcodeproj/xcuserdata/jason.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,34 @@ orderHint 0 + BitkitNotification.xcscheme_^#shared#^_ + + orderHint + 1 + + + SuppressBuildableAutocreation + + 961058DB2C355B5500E1F1D8 + + primary + + + 96FE1F602C2DE6AA006D0C8B + + primary + + + 96FE1F712C2DE6AC006D0C8B + + primary + + + 96FE1F7B2C2DE6AC006D0C8B + + primary + + diff --git a/Bitkit/Bitkit.entitlements b/Bitkit/Bitkit.entitlements index f2ef3ae0..76584c8e 100644 --- a/Bitkit/Bitkit.entitlements +++ b/Bitkit/Bitkit.entitlements @@ -2,9 +2,17 @@ - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - + aps-environment + development + com.apple.developer.aps-environment + development + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.bitkit + + com.apple.security.files.user-selected.read-only + diff --git a/Bitkit/BitkitApp.swift b/Bitkit/BitkitApp.swift index d427b6a4..83cd449c 100644 --- a/Bitkit/BitkitApp.swift +++ b/Bitkit/BitkitApp.swift @@ -7,11 +7,80 @@ import SwiftUI +//TODO move to util and show in onboarding +func requestPushNotificationPermision(completionHandler: @escaping (Bool, Error?) -> Void) { + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: completionHandler + ) + UIApplication.shared.registerForRemoteNotifications() +} + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + UNUserNotificationCenter.current().delegate = self + + //Permision is requested on coach view appearance + return true + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + Logger.debug(userInfo, context: "push notification received") + + completionHandler(UIBackgroundFetchResult.newData) + } +} + +@available(iOS 10, *) +extension AppDelegate : UNUserNotificationCenterDelegate { + // Receive displayed notifications for iOS 10 devices. + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + let userInfo = notification.request.content.userInfo + + Logger.debug(userInfo, context: "push notification received") + + // Change this to your preferred presentation option + completionHandler([[.banner, .badge, .sound]]) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let token = deviceToken.map { String(format: "%02hhx", $0) }.joined() + Logger.debug(token, context: "push token") + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + Logger.error(error) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + + Logger.debug(userInfo, context: "push notification received") + + completionHandler() + } +} + @main struct BitkitApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + var body: some Scene { WindowGroup { ContentView() + .onAppear { + //TODO move this elsewhere + requestPushNotificationPermision { (granted, error) in + if granted { + Logger.info("Push notification permission granted") + } else { + Logger.warn("Push notification permission denied") + } + } + } } } } diff --git a/Bitkit/Constants/Env.swift b/Bitkit/Constants/Env.swift index 95b18b4c..4ef40052 100644 --- a/Bitkit/Constants/Env.swift +++ b/Bitkit/Constants/Env.swift @@ -30,8 +30,10 @@ struct Env { switch network { case .regtest: //cargo run --release --bin electrs -- -vvv --jsonrpc-import --daemon-rpc-addr 127.0.0.1:18443 --cookie polaruser:polarpass - // return "https://jaybird-logical-sadly.ngrok-free.app" - return "http://localhost:3000" +// return "https://jaybird-logical-sadly.ngrok-free.app" +// return "http://127.0.0.1:3000" + + return "http://192.168.0.106:3000" case .bitcoin: fatalError("Bitcoin network not implemented") case .testnet: @@ -42,8 +44,8 @@ struct Env { } static var appStorageUrl: URL { - //TODO move to app group so files can be shared with extensions - guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + //App group so files can be shared with extensions + guard let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bitkit") else { fatalError("Could not find documents directory") } @@ -81,7 +83,11 @@ struct Env { static var trustedLnPeers: [LnPeer] { switch network { case .regtest: - return [.init(nodeId: "021de6ad59a78caf8f376cbd022e8c6ede2a1ef0a4fa035174e8b9c25ad5866584", address: "127.0.0.1:9736")] + return [ + .init(nodeId: "03e26fdad23b9e17f6a6b1dd0a019c6fcd9e778a1c2af6ae62a0951c8352efbbc3", address: "192.168.0.106:9735") +// .init(nodeId: "0218ab1da83a4768e154fada54deb5d835199aad116c4212e6844d0dce0f82cab1", address: "192.168.0.106:9737"), +// .init(nodeId: "021de6ad59a78caf8f376cbd022e8c6ede2a1ef0a4fa035174e8b9c25ad5866584", address: "192.168.0.106:9738") + ] case .bitcoin: return [] case .testnet: diff --git a/Bitkit/ContentView.swift b/Bitkit/ContentView.swift index 22b4ac04..ba274d41 100644 --- a/Bitkit/ContentView.swift +++ b/Bitkit/ContentView.swift @@ -11,62 +11,190 @@ struct ContentView: View { @StateObject var lnViewModel = LightningViewModel() @StateObject var onChainViewModel = OnChainViewModel() + @Environment(\.scenePhase) var scenePhase + + @State var showLogs = false + var body: some View { - VStack { - Group { - Text("LDK-Node running: \(lnViewModel.status?.isRunning == true ? "✅" : "❌")") - + List { + Section { + Text(lnViewModel.status?.debugState ?? "No LDK State") if let nodeId = lnViewModel.nodeId { Text("LN Node ID: \(nodeId)") + .font(.caption) .onTapGesture { UIPasteboard.general.string = nodeId } } - - if let peers = lnViewModel.peers { - ForEach(peers, id: \.nodeId) { peer in - Text("Peer: \(peer.nodeId) \(peer.address) \(peer.isConnected ? "✅" : "❌")") - } - } - + } + + Section("Balances") { if let lnBalance = lnViewModel.balance { Text("Lightning \(lnBalance.totalLightningBalanceSats)") + Text("Lightning onchain \(lnBalance.totalOnchainBalanceSats)") } - + if let onchainBalance = onChainViewModel.balance { Text("On Chain \(onchainBalance.total)") } - - if let receiveAddress = onChainViewModel.address { - Text("Receive Address: \(receiveAddress)") - .onTapGesture { - UIPasteboard.general.string = receiveAddress + } + + if let peers = lnViewModel.peers { + Section("Peers") { + ForEach(peers, id: \.nodeId) { peer in + HStack { + Text("\(peer.nodeId)@\(peer.address)") + .font(.caption2) + Spacer() + Text(peer.isConnected ? "✅" : "❌") } + } } - - Button("New Receive Address") { - try! onChainViewModel.newReceiveAddress() + } + + if let channels = lnViewModel.channels { + Section("Channels") { + ForEach(channels, id: \.channelId) { channel in + VStack { + Text(channel.counterpartyNodeId).font(.caption2) + .multilineTextAlignment(.leading) + HStack { + Text("Out: \(channel.outboundCapacityMsat)") + Spacer() + Text("In: \(channel.inboundCapacityMsat)") + Text(channel.isChannelReady ? "🟢" : "🔴") + Text(channel.isUsable ? "🟢" : "🔴") + } + + } + .onLongPressGesture { + Task { + do { + try await LightningService.shared.closeChannel(userChannelId: channel.userChannelId, counterpartyNodeId: channel.counterpartyNodeId) + Logger.info("Channel closed") + try await lnViewModel.sync() + } catch { + + } + } + } + } + + Button("Copy open channel command") { + let cmd = "lncli openchannel --node_key=\(lnViewModel.nodeId ?? "") --local_amt=200000 --push_amt=10000 --private=true --zero_conf --channel_type=anchors" + // let cmd = "lncli openchannel --node_key=\(lnViewModel.nodeId ?? "") --local_amt=200000 --push_amt=10000 --min_confs=3" + UIPasteboard.general.string = cmd + } } - - Button("Sync") { - Task { - await lnViewModel.sync() - await onChainViewModel.sync() + } + + if let receiveAddress = onChainViewModel.address { + Text("Receive Address: \(receiveAddress)") + .onTapGesture { + UIPasteboard.general.string = receiveAddress + } + } + + Button("New Receive Address") { + Task { + try await onChainViewModel.newReceiveAddress() + } + } + + Button("Create bolt11") { + Task { + let invoice = try await LightningService.shared.receive(amountSats: 123, description: "paymeplz") + Logger.info(invoice, context: "Created invoice") + UIPasteboard.general.string = invoice + } + } + + Button("Pay bolt11") { + Task { + if let invoice = UIPasteboard.general.string { + let _ = try? await LightningService.shared.send(bolt11: invoice) + } + } + } + + Button("Show Logs") { + showLogs = true + } + + Section("Transactions") { + if let payments = lnViewModel.payments { + ForEach(payments, id: \.id) { payment in + HStack { + Text("\(payment.direction == .inbound ? "⬇️" : "⬆️")") + Text("\(payment.status)") + Spacer() + Text("\(payment.amountMsat ?? 0)") + } } } } - .multilineTextAlignment(.center) - .padding() } - .padding(4) + .refreshable { + do { + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { try await lnViewModel.sync() } + group.addTask { try await onChainViewModel.sync() } + try await group.waitForAll() + } + } catch { + //TODO show an error + } + } + .sheet(isPresented: $showLogs) { + LogView() + } .onAppear { + Logger.debug("App appeared, spinning up services...") Task { do { try await lnViewModel.start() + try await lnViewModel.sync() + } catch { + Logger.error(error, context: "Failed to start LN") + } + } + + Task { + do { try await onChainViewModel.start() + try await onChainViewModel.sync() } catch { - print("Error: \(error)") + Logger.error(error, context: "Failed to start on chain") + } + } + } + .onChange(of: scenePhase) { newPhase in + if newPhase == .background { + if lnViewModel.status?.isRunning == true { + Logger.debug("App backgrounded, stopping LN service...") + Task { + do { + try await lnViewModel.stop() + } catch { + Logger.error(error, context: "Failed to stop LN") + } + } + } + return + } + + if newPhase == .active { + if lnViewModel.status?.isRunning == false { + Logger.debug("App active, starting LN service...") + Task { + do { + try await lnViewModel.start() + try await lnViewModel.sync() + } catch { + Logger.error(error, context: "Failed to start LN") + } + } } } } diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 15724d67..00de0534 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -17,47 +17,159 @@ class LightningService { private init() {} - func setup(mnemonic: String, passphrase: String?) throws { + func setup(mnemonic: String, passphrase: String?) async throws { var config = defaultConfig() config.storageDirPath = Env.ldkStorage.path config.logDirPath = Env.ldkStorage.path config.network = Env.network.ldkNetwork config.logLevel = .trace + + config.trustedPeers0conf = Env.trustedLnPeers.map({ $0.nodeId }) config.anchorChannelsConfig = .init( - trustedPeersNoReserve: Env.trustedLnPeers.map({ $0.nodeId }), - perChannelReserveSats: 2000 //TODO set correctly + trustedPeersNoReserve: Env.trustedLnPeers.map({ $0.nodeId }), + perChannelReserveSats: 1000 //TODO set correctly ) - let nodeBuilder = Builder.fromConfig(config: config) - nodeBuilder.setEsploraServer(esploraServerUrl: Env.esploraServerUrl) + let builder = Builder.fromConfig(config: config) + builder.setEsploraServer(esploraServerUrl: Env.esploraServerUrl) if let rgsServerUrl = Env.ldkRgsServerUrl { - nodeBuilder.setGossipSourceRgs(rgsServerUrl: rgsServerUrl) + builder.setGossipSourceRgs(rgsServerUrl: rgsServerUrl) + } else { + builder.setGossipSourceP2p() } - nodeBuilder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: nil) + builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: nil) + + Logger.debug(Env.ldkStorage.path, context: "LDK storage path") + + Logger.debug("Building node...") - node = try nodeBuilder.build() + try await ServiceQueue.background(.ldk) { + self.node = try builder.build() + } + + Logger.info("LDK node setup") } - func start() throws { + /// Pass onEvent when being used in the background to listen for payments, channels, closes, etc + /// - Parameter onEvent: Triggered on any LDK node event + func start(onEvent: ((Event) -> Void)? = nil) async throws { guard let node else { - //TODO throw custom error - return + throw AppError(serviceError: .nodeNotStarted) + } + + listenForEvents(onEvent: onEvent) + + Logger.debug("Starting node...") + try await ServiceQueue.background(.ldk) { + try node.start() + } + Logger.info("Node started") + + try await self.connectToTrustedPeers() + } + + func stop() async throws { + guard let node else { + throw AppError(serviceError: .nodeNotStarted) } - try node.start() - for peer in Env.trustedLnPeers { - try node.connect(nodeId: peer.nodeId, address: peer.address, persist: true) + Logger.debug("Stopping node...") + try await ServiceQueue.background(.ldk) { + try node.stop() } + Logger.info("Node stopped") } - func sync() throws { + private func connectToTrustedPeers() async throws { guard let node else { - //TODO throw custom error + throw AppError(serviceError: .nodeNotStarted) + } + + try await ServiceQueue.background(.ldk) { + for peer in Env.trustedLnPeers { + do { + try node.connect(nodeId: peer.nodeId, address: peer.address, persist: true) + Logger.info("Connected to trusted peer: \(peer.nodeId)") + } catch { + Logger.error(error, context: "Peer: \(peer.nodeId)") + } + } + } + } + + /// Temp fix for regtest where nodes might not agree on current fee rates + private func setMaxDustHtlcExposureForCurrentChannels() throws { + guard Env.network == .regtest else { + Logger.debug("Not updating channel config for non-regtest network") return } - try node.syncWallets() + + guard let node else { + throw AppError(serviceError: .nodeNotStarted) + } + + for channel in node.listChannels() { + let config = channel.config + config.setMaxDustHtlcExposureFromFixedLimit(limitMsat: 999999 * 1000) + try? node.updateChannelConfig(userChannelId: channel.userChannelId, counterpartyNodeId: channel.counterpartyNodeId, channelConfig: config) + Logger.info("Updated channel config for: \(channel.userChannelId)") + } + } + + func sync() async throws { + guard let node else { + throw AppError(serviceError: .nodeNotStarted) + } + + Logger.debug("Syncing LDK...") + try await ServiceQueue.background(.ldk) { + try node.syncWallets() + try? self.setMaxDustHtlcExposureForCurrentChannels() + } + Logger.info("LDK synced") + } + + func receive(amountSats: UInt64, description: String, expirySecs: UInt32 = 3600) async throws -> Bolt11Invoice { + guard let node else { + throw AppError(serviceError: .nodeNotStarted) + } + + return try await ServiceQueue.background(.ldk) { + return try node + .bolt11Payment() + .receive( + amountMsat: amountSats * 1000, + description: description, + expirySecs: expirySecs + ) + } + } + + func send(bolt11: Bolt11Invoice) async throws -> PaymentHash { + guard let node else { + throw AppError(serviceError: .nodeNotStarted) + } + + //Check if peer is connected + + return try await ServiceQueue.background(.ldk) { + return try node.bolt11Payment().send(invoice: bolt11) + } + } + + func closeChannel(userChannelId: ChannelId, counterpartyNodeId: PublicKey) async throws { + guard let node else { + throw AppError(serviceError: .nodeNotStarted) + } + + return try await ServiceQueue.background(.ldk) { + try node.closeChannel( + userChannelId: userChannelId, + counterpartyNodeId: counterpartyNodeId + ) + } } } @@ -67,6 +179,50 @@ extension LightningService { var balances: BalanceDetails? { node?.listBalances() } var status: NodeStatus? { node?.status() } var peers: [PeerDetails]? { node?.listPeers() } - var Channels: [ChannelDetails]? { node?.listChannels() } + var channels: [ChannelDetails]? { node?.listChannels() } var payments: [PaymentDetails]? { node?.listPayments() } } + +//MARK: Events +extension LightningService { + func listenForEvents(onEvent: ((Event) -> Void)? = nil) { + Task { + while true { + guard let node = self.node else { + Logger.error("LDK node not started") + return + } + + let event = await node.nextEventAsync() + onEvent?(event) + + //TODO actual event handler + switch event { + case .paymentSuccessful(paymentId: let paymentId, paymentHash: let paymentHash, feePaidMsat: let feePaidMsat): + Logger.info("✅ Payment successful: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) feePaidMsat: \(feePaidMsat ?? 0)") + break + case .paymentFailed(paymentId: let paymentId, paymentHash: let paymentHash, reason: let reason): + Logger.info("❌ Payment failed: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) reason: \(reason.debugDescription)") + break + case .paymentReceived(paymentId: let paymentId, paymentHash: let paymentHash, amountMsat: let amountMsat): + Logger.info("🤑 Payment received: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) amountMsat: \(amountMsat)") + break + case .paymentClaimable(paymentId: let paymentId, paymentHash: let paymentHash, claimableAmountMsat: let claimableAmountMsat, claimDeadline: let claimDeadline): + Logger.info("🫰 Payment claimable: paymentId: \(paymentId) paymentHash: \(paymentHash) claimableAmountMsat: \(claimableAmountMsat)") + break + case .channelPending(channelId: let channelId, userChannelId: let userChannelId, formerTemporaryChannelId: let formerTemporaryChannelId, counterpartyNodeId: let counterpartyNodeId, fundingTxo: let fundingTxo): + Logger.info("⏳ Channel pending: channelId: \(channelId) userChannelId: \(userChannelId) formerTemporaryChannelId: \(formerTemporaryChannelId) counterpartyNodeId: \(counterpartyNodeId) fundingTxo: \(fundingTxo)") + break + case .channelReady(channelId: let channelId, userChannelId: let userChannelId, counterpartyNodeId: let counterpartyNodeId): + Logger.info("👐 Channel ready: channelId: \(channelId) userChannelId: \(userChannelId) counterpartyNodeId: \(counterpartyNodeId ?? "?")") + break + case .channelClosed(channelId: let channelId, userChannelId: let userChannelId, counterpartyNodeId: let counterpartyNodeId, reason: let reason): + Logger.info("⛔ Channel closed: channelId: \(channelId) userChannelId: \(userChannelId) counterpartyNodeId: \(counterpartyNodeId ?? "?") reason: \(reason.debugDescription)") + break + } + + node.eventHandled() + } + } + } +} diff --git a/Bitkit/Services/OnChainService.swift b/Bitkit/Services/OnChainService.swift index 835d994b..615585bb 100644 --- a/Bitkit/Services/OnChainService.swift +++ b/Bitkit/Services/OnChainService.swift @@ -29,19 +29,22 @@ class OnChainService { blockchainConfig = BlockchainConfig.esplora(config: esploraConfig) } - func createWallet(mnemonic: String, passphrase: String?) throws { - let mnemonic = try Mnemonic.fromString(mnemonic: mnemonic) + func createWallet(mnemonic: String, passphrase: String?) async throws { + let mnemonic = try Mnemonic.fromString(mnemonic: "\(mnemonic)\(passphrase == nil ? "" : " \(passphrase!)")") + let secretKey = DescriptorSecretKey( network: Env.network.bdkNetwork, mnemonic: mnemonic, password: passphrase ) - let descriptor = Descriptor.newBip86( + + let descriptor = Descriptor.newBip84( secretKey: secretKey, keychain: .external, network: Env.network.bdkNetwork ) - let changeDescriptor = Descriptor.newBip86( + + let changeDescriptor = Descriptor.newBip84( secretKey: secretKey, keychain: .internal, network: Env.network.bdkNetwork @@ -49,30 +52,45 @@ class OnChainService { //TODO save to keychain - wallet = try Wallet( - descriptor: descriptor, - changeDescriptor: changeDescriptor, - network: Env.network.bdkNetwork, - databaseConfig: .memory //TODO use sqlite - ) + Logger.debug("Creating onchain wallet...") + + try await ServiceQueue.background(.bdk) { + self.wallet = try Wallet( + descriptor: descriptor, + changeDescriptor: changeDescriptor, + network: Env.network.bdkNetwork, + databaseConfig: .memory //TODO use sqlite + ) + } + + Logger.info("Onchain wallet created") } - func getAddress() throws -> String { + func getAddress() async throws -> String { guard let wallet else { - //TODO throw custom error - return "error" + throw AppError(serviceError: .onchainWalletNotCreated) + } + + return try await ServiceQueue.background(.bdk) { + let addressInfo = try wallet.getAddress(addressIndex: .new) + return addressInfo.address.asString() } - let addressInfo = try wallet.getAddress(addressIndex: .new) - return addressInfo.address.asString() } - func sync() throws { + func sync() async throws { guard let wallet, let blockchainConfig else { - //TODO throw custom error - return + throw AppError(serviceError: .onchainWalletNotCreated) } + + Logger.debug("Syncing BDK...") + let blockchain = try Blockchain(config: blockchainConfig) - try wallet.sync(blockchain: blockchain, progress: nil) + + try await ServiceQueue.background(.bdk) { + try wallet.sync(blockchain: blockchain, progress: nil) + } + + Logger.info("BDK synced") } } diff --git a/Bitkit/Services/ServiceQueue.swift b/Bitkit/Services/ServiceQueue.swift new file mode 100644 index 00000000..cc734ff4 --- /dev/null +++ b/Bitkit/Services/ServiceQueue.swift @@ -0,0 +1,45 @@ +// +// ServiceQueue.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/04. +// + +import Foundation + +/// Handles app services each on it's own dedicated queue +class ServiceQueue { + private static let ldkQueue = DispatchQueue(label: "ldk-queue", qos: .utility) + private static let bdkQueue = DispatchQueue(label: "bdk-queue", qos: .utility) + + private init() {} + + enum ServiceTypes { + case ldk + case bdk + + var queue: DispatchQueue { + switch self { + case .ldk: + return ServiceQueue.ldkQueue + case .bdk: + return ServiceQueue.bdkQueue + } + } + } + + static func background(_ service: ServiceTypes, _ blocking: @escaping () throws -> T) async throws -> T { + try await withCheckedThrowingContinuation { continuation in + service.queue.async { + do { + let res = try blocking() + continuation.resume(with: .success(res)) + } catch { + let appError = AppError(error: error) + Logger.error("\(appError.message) [\(appError.debugMessage ?? "")]", context: "ServiceQueue: \(service)") + continuation.resume(throwing: appError) + } + } + } + } +} diff --git a/Bitkit/Utilities/Errors.swift b/Bitkit/Utilities/Errors.swift new file mode 100644 index 00000000..3effd0b6 --- /dev/null +++ b/Bitkit/Utilities/Errors.swift @@ -0,0 +1,263 @@ +// +// Errors.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/04. +// + +import Foundation +import LDKNode +import BitcoinDevKit + +enum CustomServiceError: Error { + case nodeNotStarted + case onchainWalletNotCreated +} + +/// Translates LDK and BDK error messages into translated messages that can be displayed to end users +struct AppError: LocalizedError { + let message: String + let debugMessage: String? + + var errorDescription: String? { + return NSLocalizedString(message, comment: "") + } + + /// Pass any LDK or BDK error to get a translated error message + /// - Parameter error: any error + init(error: Error) { + if let ldkBuildError = error as? BuildError { + self.init(ldkBuildError: ldkBuildError) + return + } + + if let ldkError = error as? NodeError { + self.init(ldkError: ldkError) + return + } + + if let bdkError = error as? BdkError { + self.init(bdkError: bdkError) + return + } + + self.init(message: "Unknown error", debugMessage: error.localizedDescription) + } + + init(message: String, debugMessage: String?) { + self.message = message + self.debugMessage = debugMessage + + Logger.error("\(message) [\(debugMessage ?? "")]", context: "generic app error") + } + + init(serviceError: CustomServiceError) { + switch serviceError { + case .nodeNotStarted: + message = "Node is not started" + debugMessage = nil + case .onchainWalletNotCreated: + message = "Onchain wallet not created" + debugMessage = nil + } + + Logger.error("\(message) [\(debugMessage ?? "")]", context: "service error") + } + + private init(bdkError: BdkError) { + message = "Bdk error" + debugMessage = bdkError.localizedDescription + //TODO support all message types in switch case +// switch bdkError as BdkError { +// case .Bip32(message: let bdkMessage): +// message = "BIP32 error" +// debugMessage = bdkMessage +// } + + Logger.error("\(message) [\(debugMessage ?? "")]", context: "BdkError") + } + + private init(ldkBuildError: BuildError) { + switch ldkBuildError as BuildError { + case .InvalidChannelMonitor(message: let ldkMessage): + message = "Invalid channel monitor" + debugMessage = ldkMessage + case .InvalidSeedBytes(message: let ldkMessage): + message = "Invalid seed bytes" + debugMessage = ldkMessage + case .InvalidSeedFile(message: let ldkMessage): + message = "Invalid seed file" + debugMessage = ldkMessage + case .InvalidSystemTime(message: let ldkMessage): + message = "Invalid system time" + debugMessage = ldkMessage + case .InvalidListeningAddresses(message: let ldkMessage): + message = "Invalid listening addresses" + debugMessage = ldkMessage + case .ReadFailed(message: let ldkMessage): + message = "Read failed" + debugMessage = ldkMessage + case .WriteFailed(message: let ldkMessage): + message = "Write failed" + debugMessage = ldkMessage + case .StoragePathAccessFailed(message: let ldkMessage): + message = "Storage path access failed" + debugMessage = ldkMessage + case .KvStoreSetupFailed(message: let ldkMessage): + message = "KV store setup failed" + debugMessage = ldkMessage + case .WalletSetupFailed(message: let ldkMessage): + message = "Wallet setup failed" + debugMessage = ldkMessage + case .LoggerSetupFailed(message: let ldkMessage): + message = "Logger setup failed" + debugMessage = ldkMessage + } + } + + private init(ldkError: NodeError) { + switch ldkError as NodeError { + case .AlreadyRunning(message: let ldkMessage): + message = "Node is already running" + debugMessage = ldkMessage + break; + case .NotRunning(message: let ldkMessage): + message = "Node is not running" + debugMessage = ldkMessage + case .OnchainTxCreationFailed(message: let ldkMessage): + message = "Failed to create onchain transaction" + debugMessage = ldkMessage + case .ConnectionFailed(message: let ldkMessage): + message = "Failed to connect to node" + debugMessage = ldkMessage + case .InvoiceCreationFailed(message: let ldkMessage): + message = "Failed to create invoice" + debugMessage = ldkMessage + case .InvoiceRequestCreationFailed(message: let ldkMessage): + message = "Failed to create invoice request" + debugMessage = ldkMessage + case .OfferCreationFailed(message: let ldkMessage): + message = "Failed to create offer" + debugMessage = ldkMessage + case .RefundCreationFailed(message: let ldkMessage): + message = "Failed to create refund" + debugMessage = ldkMessage + case .PaymentSendingFailed(message: let ldkMessage): + message = "Failed to send payment" + debugMessage = ldkMessage + case .ProbeSendingFailed(message: let ldkMessage): + message = "Failed to send probe" + debugMessage = ldkMessage + case .ChannelCreationFailed(message: let ldkMessage): + message = "Failed to create channel" + debugMessage = ldkMessage + case .ChannelClosingFailed(message: let ldkMessage): + message = "Failed to close channel" + debugMessage = ldkMessage + case .ChannelConfigUpdateFailed(message: let ldkMessage): + message = "Failed to update channel config" + debugMessage = ldkMessage + case .PersistenceFailed(message: let ldkMessage): + message = "Failed to persist data" + debugMessage = ldkMessage + case .FeerateEstimationUpdateFailed(message: let ldkMessage): + message = "Failed to update feerate estimation" + debugMessage = ldkMessage + case .FeerateEstimationUpdateTimeout(message: let ldkMessage): + message = "Failed to update feerate estimation due to timeout" + debugMessage = ldkMessage + case .WalletOperationFailed(message: let ldkMessage): + message = "Failed to perform wallet operation" + debugMessage = ldkMessage + case .WalletOperationTimeout(message: let ldkMessage): + message = "Failed to perform wallet operation due to timeout" + debugMessage = ldkMessage + case .OnchainTxSigningFailed(message: let ldkMessage): + message = "Failed to sign onchain transaction" + debugMessage = ldkMessage + case .MessageSigningFailed(message: let ldkMessage): + message = "Failed to sign message" + debugMessage = ldkMessage + case .TxSyncFailed(message: let ldkMessage): + message = "Failed to sync transaction" + debugMessage = ldkMessage + case .TxSyncTimeout(message: let ldkMessage): + message = "Failed to sync transaction due to timeout" + debugMessage = ldkMessage + case .GossipUpdateFailed(message: let ldkMessage): + message = "Failed to update gossip" + debugMessage = ldkMessage + case .GossipUpdateTimeout(message: let ldkMessage): + message = "Failed to update gossip due to timeout" + debugMessage = ldkMessage + case .LiquidityRequestFailed(message: let ldkMessage): + message = "Failed to request liquidity" + debugMessage = ldkMessage + case .InvalidAddress(message: let ldkMessage): + message = "Invalid address" + debugMessage = ldkMessage + case .InvalidSocketAddress(message: let ldkMessage): + message = "Invalid socket address" + debugMessage = ldkMessage + case .InvalidPublicKey(message: let ldkMessage): + message = "Invalid public key" + debugMessage = ldkMessage + case .InvalidSecretKey(message: let ldkMessage): + message = "Invalid secret key" + debugMessage = ldkMessage + case .InvalidOfferId(message: let ldkMessage): + message = "Invalid offer ID" + debugMessage = ldkMessage + case .InvalidNodeId(message: let ldkMessage): + message = "Invalid node ID" + debugMessage = ldkMessage + case .InvalidPaymentId(message: let ldkMessage): + message = "Invalid payment ID" + debugMessage = ldkMessage + case .InvalidPaymentHash(message: let ldkMessage): + message = "Invalid payment hash" + debugMessage = ldkMessage + case .InvalidPaymentPreimage(message: let ldkMessage): + message = "Invalid payment preimage" + debugMessage = ldkMessage + case .InvalidPaymentSecret(message: let ldkMessage): + message = "Invalid payment secret" + debugMessage = ldkMessage + case .InvalidAmount(message: let ldkMessage): + message = "Invalid amount" + debugMessage = ldkMessage + case .InvalidInvoice(message: let ldkMessage): + message = "Invalid invoice" + debugMessage = ldkMessage + case .InvalidOffer(message: let ldkMessage): + message = "Invalid offer" + debugMessage = ldkMessage + case .InvalidRefund(message: let ldkMessage): + message = "Invalid refund" + debugMessage = ldkMessage + case .InvalidChannelId(message: let ldkMessage): + message = "Invalid channel ID" + debugMessage = ldkMessage + case .InvalidNetwork(message: let ldkMessage): + message = "Invalid network" + debugMessage = ldkMessage + case .DuplicatePayment(message: let ldkMessage): + message = "Duplicate payment" + debugMessage = ldkMessage + case .UnsupportedCurrency(message: let ldkMessage): + message = "Unsupported currency" + debugMessage = ldkMessage + case .InsufficientFunds(message: let ldkMessage): + message = "Insufficient funds" + debugMessage = ldkMessage + case .LiquiditySourceUnavailable(message: let ldkMessage): + message = "Liquidity source unavailable" + debugMessage = ldkMessage + case .LiquidityFeeTooHigh(message: let ldkMessage): + message = "Liquidity fee too high" + debugMessage = ldkMessage + } + + Logger.error("\(message) [\(debugMessage ?? "")]", context: "ldk-node error") + } +} diff --git a/Bitkit/Utilities/Logger.swift b/Bitkit/Utilities/Logger.swift new file mode 100644 index 00000000..5b0dd57a --- /dev/null +++ b/Bitkit/Utilities/Logger.swift @@ -0,0 +1,44 @@ +// +// Logger.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/16. +// + +import Foundation + +class Logger { + private init() {} + static let queue = DispatchQueue (label: "bitkit.log", qos: .utility) + + static func info(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + handle("INFOℹ️: \(message)", context: context, file: file, function: function, line: line) + } + + static func debug(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + handle("DEBUG: \(message)", context: context, file: file, function: function, line: line) + } + + static func warn(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + handle("WARN⚠️: \(message)", context: context, file: file, function: function, line: line) + } + + static func error(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + handle("ERROR❌: \(message)", context: context, file: file, function: function, line: line) + } + + static func test(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + handle("🧪🧪🧪: \(message)", context: context, file: file, function: function, line: line) + } + + private static func handle(_ message: Any, context: String = "", file: String = #file, function: String = #function, line: Int = #line) { + let fileName = URL(fileURLWithPath: file).lastPathComponent + let line = "\(message) \(context == "" ? "" : "- \(context) ")[\(fileName): \(function) line: \(line)]" + + print(line) + + queue.async { + //TODO write to file + } + } +} diff --git a/Bitkit/ViewModels/LightningViewModel.swift b/Bitkit/ViewModels/LightningViewModel.swift index 6e168f6c..00a7b04e 100644 --- a/Bitkit/ViewModels/LightningViewModel.swift +++ b/Bitkit/ViewModels/LightningViewModel.swift @@ -10,31 +10,73 @@ import LDKNode @MainActor class LightningViewModel: ObservableObject { + @Published var isSyncing = false @Published var status: NodeStatus? @Published var nodeId: String? @Published var balance: BalanceDetails? @Published var peers: [PeerDetails]? - + @Published var channels: [ChannelDetails]? + @Published var payments: [PaymentDetails]? + func start() async throws { - let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() + let mnemonic = "always coconut smooth scatter steel web version exist broken motion damage board trap dinosaur include alone dust flag paddle give divert journey garden bench" // = generateEntropyMnemonic() let passphrase: String? = nil - try LightningService.shared.setup(mnemonic: mnemonic, passphrase: passphrase) - try LightningService.shared.start() - await sync() + syncState() + try await LightningService.shared.setup(mnemonic: mnemonic, passphrase: passphrase) + try await LightningService.shared.start(onEvent: { _ in + Task { @MainActor in + self.syncState() + } + }) + syncState() + + //TODO listen on LDK events to sync UI state + } + + func stop() async throws { + try await LightningService.shared.stop() + syncState() } - func sync() async { + func sync() async throws { + isSyncing = true + syncState() + do { - try LightningService.shared.sync() - status = LightningService.shared.status - nodeId = LightningService.shared.nodeId - balance = LightningService.shared.balances - peers = LightningService.shared.peers - - //TODO sync everything else for the UI + try await LightningService.shared.sync() + isSyncing = false } catch { - print("Error: \(error)") + isSyncing = false + throw error } + + syncState() + } + + private func syncState() { + status = LightningService.shared.status + nodeId = LightningService.shared.nodeId + balance = LightningService.shared.balances + peers = LightningService.shared.peers + channels = LightningService.shared.channels + payments = LightningService.shared.payments + } +} + +extension NodeStatus { + var debugState: String { + var debug = """ +Running: \(isRunning ? "✅" : "❌") +Current best block \(currentBestBlock.height) +""" + + if let latestWalletSyncTimestamp { + debug += "\nLast synced \(Date(timeIntervalSince1970: TimeInterval(latestWalletSyncTimestamp)).description)\n" + } else { + debug += "\nLast synced never\n" + } + + return debug } } diff --git a/Bitkit/ViewModels/OnChainViewModel.swift b/Bitkit/ViewModels/OnChainViewModel.swift index f5e98573..cfa6a06b 100644 --- a/Bitkit/ViewModels/OnChainViewModel.swift +++ b/Bitkit/ViewModels/OnChainViewModel.swift @@ -10,28 +10,38 @@ import BitcoinDevKit @MainActor class OnChainViewModel: ObservableObject { + @Published var isSyncing = false @Published var balance: Balance? @Published var address: String? func start() async throws { - let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() + let mnemonic = "always coconut smooth scatter steel web version exist broken motion damage board trap dinosaur include alone dust flag paddle give divert journey garden bench" // = generateEntropyMnemonic() let passphrase: String? = nil try OnChainService.shared.setup() - try OnChainService.shared.createWallet(mnemonic: mnemonic, passphrase: passphrase) - await sync() + try await OnChainService.shared.createWallet(mnemonic: mnemonic, passphrase: passphrase) + syncState() } - func newReceiveAddress() throws { - address = try OnChainService.shared.getAddress() + func newReceiveAddress() async throws { + address = try await OnChainService.shared.getAddress() } - func sync() async { + func sync() async throws { + isSyncing = true + syncState() do { - try OnChainService.shared.sync() - balance = OnChainService.shared.balance + try await OnChainService.shared.sync() + isSyncing = false + syncState() } catch { - print("Error: \(error)") + isSyncing = false + syncState() + throw error } } + + private func syncState() { + balance = OnChainService.shared.balance + } } diff --git a/Bitkit/Views/LogView.swift b/Bitkit/Views/LogView.swift new file mode 100644 index 00000000..def2dd35 --- /dev/null +++ b/Bitkit/Views/LogView.swift @@ -0,0 +1,44 @@ +// +// LogView.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/16. +// + +import SwiftUI + +struct LogView: View { + @State var lines: [String] = [] + + var body: some View { + List { + ForEach(lines, id: \.self) { line in + Text(line) + .font(.system(size: 8)) + .multilineTextAlignment(.leading) + .foregroundColor(.green) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .listStyle(.plain) + .onAppear { + loadLog() + } + } + + func loadLog() { + let dir = Env.ldkStorage + let fileURL = dir.appendingPathComponent("ldk_node_latest.log") + + do { + let text = try String(contentsOf: fileURL, encoding: .utf8) + lines = text.components(separatedBy: "\n").map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) + } catch { + lines = ["Failed to load log file"] + } + } +} + +#Preview { + LogView() +} diff --git a/BitkitNotification/BitkitNotification.entitlements b/BitkitNotification/BitkitNotification.entitlements new file mode 100644 index 00000000..4fca2ce3 --- /dev/null +++ b/BitkitNotification/BitkitNotification.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.bitkit + + + diff --git a/BitkitNotification/Info.plist b/BitkitNotification/Info.plist new file mode 100644 index 00000000..9f52b5a4 --- /dev/null +++ b/BitkitNotification/Info.plist @@ -0,0 +1,18 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/BitkitNotification/NotificationService.swift b/BitkitNotification/NotificationService.swift new file mode 100644 index 00000000..c703a4a9 --- /dev/null +++ b/BitkitNotification/NotificationService.swift @@ -0,0 +1,111 @@ +// +// NotificationService.swift +// BitkitNotification +// +// Created by Jason van den Berg on 2024/07/03. +// + +import UserNotifications +import LDKNode + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + Task { + do { + let mnemonic = "always coconut smooth scatter steel web version exist broken motion damage board trap dinosaur include alone dust flag paddle give divert journey garden bench" // = generateEntropyMnemonic() + let passphrase: String? = nil + + try await LightningService.shared.setup(mnemonic: mnemonic, passphrase: passphrase) + + try await LightningService.shared.start { event in + self.handleLdkEvent(event: event) + } + } catch { + bestAttemptContent?.title = "Lightning error" + bestAttemptContent?.body = error.localizedDescription + + Logger.error(error, context: "failed to setup node in notification service") + dumpLdkLogs() + await deliver() + } + } + } + + func handleLdkEvent(event: Event) { + switch event { + case .paymentReceived(paymentId: let paymentId, paymentHash: let paymentHash, amountMsat: let amountMsat): + self.bestAttemptContent?.title = "Payment Received" + self.bestAttemptContent?.body = "⚡ \(amountMsat / 1000)" + Task { + await self.deliver() + } + break + case .channelPending(channelId: let channelId, userChannelId: let userChannelId, formerTemporaryChannelId: let formerTemporaryChannelId, counterpartyNodeId: let counterpartyNodeId, fundingTxo: let fundingTxo): + self.bestAttemptContent?.title = "Channel Opened" + self.bestAttemptContent?.body = "Pending" + //Don't deliver, give a chance for channelReady event to update the content + break + case .channelReady(channelId: let channelId, userChannelId: let userChannelId, counterpartyNodeId: let counterpartyNodeId): + self.bestAttemptContent?.title = "Channel ready" + self.bestAttemptContent?.body = "Usable" + Task { + await self.deliver() + } + break + case .channelClosed(channelId: let channelId, userChannelId: let userChannelId, counterpartyNodeId: let counterpartyNodeId, reason: let reason): + self.bestAttemptContent?.title = "Channel closed" + self.bestAttemptContent?.body = reason.debugDescription //TODO: Reason string + Task { + await self.deliver() + } + break + case .paymentSuccessful(_, _, _): + break + case .paymentFailed(_, _, _): + break + case .paymentClaimable(_, _, _, _): + break + } + } + + func deliver() async { + try? await LightningService.shared.stop() + + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + //TODO: Stop LDK + + contentHandler(bestAttemptContent) + } + } + + func dumpLdkLogs() { + let dir = Env.ldkStorage + let fileURL = dir.appendingPathComponent("ldk_node_latest.log") + + do { + let text = try String(contentsOf: fileURL, encoding: .utf8) + let lines = text.components(separatedBy: "\n").map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) + print("*****LDK-NODE LOG******") + lines.suffix(20).forEach { line in + print(line) + } + } catch { + Logger.error(error, context: "failed to load ldk log file") + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } +} diff --git a/test-push-server/.gitignore b/test-push-server/.gitignore new file mode 100644 index 00000000..bf828a9d --- /dev/null +++ b/test-push-server/.gitignore @@ -0,0 +1,3 @@ +node_modules +certs +.idea diff --git a/test-push-server/helpers.js b/test-push-server/helpers.js new file mode 100644 index 00000000..b441e9d4 --- /dev/null +++ b/test-push-server/helpers.js @@ -0,0 +1,20 @@ +const { defaultPaymentAlert, appBundleID } = require('./settings'); + +const createPushData = (payload) => { + return { + topic: appBundleID, + title: defaultPaymentAlert.title, + body: defaultPaymentAlert.body, + alert: { // iOS only + ...defaultPaymentAlert, + payload + }, + priority: 'high', + contentAvailable: 1, + mutableContent: 1, + badge: 0, + sound: 'ping.aiff', + }; +}; + +module.exports = {createPushData}; diff --git a/test-push-server/index.js b/test-push-server/index.js new file mode 100644 index 00000000..c52eb769 --- /dev/null +++ b/test-push-server/index.js @@ -0,0 +1,31 @@ +const PushNotifications = require('node-pushnotifications'); + +const { pushSettings } = require('./settings'); +const { createPushData } = require('./helpers'); + +const push = new PushNotifications(pushSettings); + +const hardcodedpushtoken = "df6a15e37fe90bd0f71e919823ea19171e30fbc5632130e5e8e68f17dc76105e"; + +const data = createPushData({ + type: 'payment' +}); + +console.log("Sending test"); +push.send(hardcodedpushtoken, data) + .then((results) => { + if (results[0].success) { + console.log('SENT!'); + return; + } + + console.log("No success from APS."); + console.error(JSON.stringify(results)); + }) + .catch((error) => { + console.error("Error sending push notification."); + console.log(JSON.stringify(error)); +}).finally(() => { + console.log("Done."); + process.exit(0); +}); diff --git a/test-push-server/package-lock.json b/test-push-server/package-lock.json new file mode 100644 index 00000000..2a3a2aae --- /dev/null +++ b/test-push-server/package-lock.json @@ -0,0 +1,1401 @@ +{ + "name": "push-notification-server", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "push-notification-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^16.3.1", + "node-pushnotifications": "^2.0.3" + } + }, + "node_modules/@parse/node-apn": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", + "integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==", + "dependencies": { + "debug": "4.3.3", + "jsonwebtoken": "8.5.1", + "node-forge": "1.3.0", + "verror": "1.10.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http_ece": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz", + "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==", + "dependencies": { + "urlsafe-base64": "~1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jsprim/node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-adm": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-adm/-/node-adm-0.9.1.tgz", + "integrity": "sha512-npudU9I1l6wUEUcZme6yhe1WTi48nMSM7v2BxqGtE7hO12knzCnRj+IeUmaDdNzNMNiOU8ZYqN6PE6PXX2F/3w==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gcm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-gcm/-/node-gcm-1.0.5.tgz", + "integrity": "sha512-yTN0q31xUxA3MwStkvT4UIgHw1khg8rrKywMdSEVjmMpK/aWIHVVZ4ia3P41+ppPH3O8+teMHs/bk3xZ8C4n1A==", + "dependencies": { + "debug": "^3.1.0", + "lodash": "^4.17.21", + "request": "^2.88.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/node-gcm/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/node-pushnotifications": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-pushnotifications/-/node-pushnotifications-2.0.3.tgz", + "integrity": "sha512-X0Ji7yNN25u0aVAxK0ovwU1j/LVw13QGOGli4sPR4pS1oxYJWiP4SpfvvwXoVO0gru8XV2RXHfXUZsVvmiTzWg==", + "dependencies": { + "@parse/node-apn": "5.1.3", + "node-adm": "0.9.1", + "node-gcm": "1.0.5", + "ramda": "0.28.0", + "web-push": "3.4.5", + "wns": "0.5.4" + }, + "engines": { + "node": ">=12.x.x" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ramda": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", + "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/web-push": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.4.5.tgz", + "integrity": "sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g==", + "dependencies": { + "asn1.js": "^5.3.0", + "http_ece": "1.1.0", + "https-proxy-agent": "^5.0.0", + "jws": "^4.0.0", + "minimist": "^1.2.5", + "urlsafe-base64": "^1.0.0" + }, + "bin": { + "web-push": "src/cli.js" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/web-push/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/web-push/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/wns": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/wns/-/wns-0.5.4.tgz", + "integrity": "sha512-WYiJ7khIwUGBD5KAm+YYmwJDDRzFRs4YGAjtbFSoRIdbn9Jcix3p9khJmpvBTXGommaKkvduAn+pc9l4d9yzVQ==", + "engines": { + "node": ">= 0.6.17" + } + } + }, + "dependencies": { + "@parse/node-apn": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", + "integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==", + "requires": { + "debug": "4.3.3", + "jsonwebtoken": "8.5.1", + "node-forge": "1.3.0", + "verror": "1.10.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "http_ece": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz", + "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==", + "requires": { + "urlsafe-base64": "~1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + } + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "dependencies": { + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-adm": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-adm/-/node-adm-0.9.1.tgz", + "integrity": "sha512-npudU9I1l6wUEUcZme6yhe1WTi48nMSM7v2BxqGtE7hO12knzCnRj+IeUmaDdNzNMNiOU8ZYqN6PE6PXX2F/3w==" + }, + "node-forge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" + }, + "node-gcm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-gcm/-/node-gcm-1.0.5.tgz", + "integrity": "sha512-yTN0q31xUxA3MwStkvT4UIgHw1khg8rrKywMdSEVjmMpK/aWIHVVZ4ia3P41+ppPH3O8+teMHs/bk3xZ8C4n1A==", + "requires": { + "debug": "^3.1.0", + "lodash": "^4.17.21", + "request": "^2.88.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "node-pushnotifications": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-pushnotifications/-/node-pushnotifications-2.0.3.tgz", + "integrity": "sha512-X0Ji7yNN25u0aVAxK0ovwU1j/LVw13QGOGli4sPR4pS1oxYJWiP4SpfvvwXoVO0gru8XV2RXHfXUZsVvmiTzWg==", + "requires": { + "@parse/node-apn": "5.1.3", + "node-adm": "0.9.1", + "node-gcm": "1.0.5", + "ramda": "0.28.0", + "web-push": "3.4.5", + "wns": "0.5.4" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + }, + "ramda": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", + "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "web-push": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.4.5.tgz", + "integrity": "sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g==", + "requires": { + "asn1.js": "^5.3.0", + "http_ece": "1.1.0", + "https-proxy-agent": "^5.0.0", + "jws": "^4.0.0", + "minimist": "^1.2.5", + "urlsafe-base64": "^1.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "wns": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/wns/-/wns-0.5.4.tgz", + "integrity": "sha512-WYiJ7khIwUGBD5KAm+YYmwJDDRzFRs4YGAjtbFSoRIdbn9Jcix3p9khJmpvBTXGommaKkvduAn+pc9l4d9yzVQ==" + } + } +} diff --git a/test-push-server/package.json b/test-push-server/package.json new file mode 100644 index 00000000..559aa6c1 --- /dev/null +++ b/test-push-server/package.json @@ -0,0 +1,25 @@ +{ + "name": "push-notification-server", + "version": "1.0.0", + "description": "Push notification server for lightning wallet", + "main": "index.js", + "scripts": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/Jasonvdb/lightning-mobile-push-payments.git" + }, + "keywords": [ + "bitcoin", + "lightning" + ], + "author": "Jason", + "license": "ISC", + "bugs": { + "url": "https://github.com/Jasonvdb/lightning-mobile-push-payments/issues" + }, + "homepage": "https://github.com/Jasonvdb/lightning-mobile-push-payments#readme", + "dependencies": { + "dotenv": "^16.3.1", + "node-pushnotifications": "^2.0.3" + } +} diff --git a/test-push-server/settings.js b/test-push-server/settings.js new file mode 100644 index 00000000..2bf6f44b --- /dev/null +++ b/test-push-server/settings.js @@ -0,0 +1,20 @@ +const pushSettings = { + apn: { + token: { + key: './certs/AuthKey_DH6VTRG952.p8', // optionally: fs.readFileSync('./certs/key.p8') + keyId: 'DH6VTRG952', + teamId: 'KYH47R284B', + }, + production: false // true for APN production environment, false for APN sandbox environment, + }, + isAlwaysUseFCM: false, // true all messages will be sent through node-gcm (which actually uses FCM) +}; + +const defaultPaymentAlert = { + title: 'Incoming LN Payment failed', + body: 'Please open app and ask sender to try again.' +} + +const appBundleID = 'to.Bitkit.native'; + +module.exports = {pushSettings, defaultPaymentAlert, appBundleID};