Commit 441d532a authored by Jose Blaya's avatar Jose Blaya
Browse files

Friend Referral API

parent c4f33c3d
......@@ -280,6 +280,12 @@
DD8BF3CB219C6BAA0041357C /* ConfirmVPNPlanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8BF3CA219C6BAA0041357C /* ConfirmVPNPlanViewController.swift */; };
DDA4A7BE21F5C31400A02ACD /* IKEv2Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA4A7BD21F5C31400A02ACD /* IKEv2Profile.swift */; };
DDA4A7BF21F5C31B00A02ACD /* IKEv2Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA4A7BD21F5C31400A02ACD /* IKEv2Profile.swift */; };
DDC0840D22EB06F400DA2701 /* Invites.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0840C22EB06F400DA2701 /* Invites.swift */; };
DDC0840E22EB06F400DA2701 /* Invites.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0840C22EB06F400DA2701 /* Invites.swift */; };
DDC0841022EB077400DA2701 /* InvitesInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0840F22EB077400DA2701 /* InvitesInformation.swift */; };
DDC0841122EB077400DA2701 /* InvitesInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0840F22EB077400DA2701 /* InvitesInformation.swift */; };
DDC0841322EB07FB00DA2701 /* GlossInvitesInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0841222EB07FB00DA2701 /* GlossInvitesInformation.swift */; };
DDC0841422EB07FB00DA2701 /* GlossInvitesInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC0841222EB07FB00DA2701 /* GlossInvitesInformation.swift */; };
DDD824E32189969400151709 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD824E22189969400151709 /* Preset.swift */; };
DDD824E5218996CD00151709 /* Pages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD824E4218996CD00151709 /* Pages.swift */; };
DDD824E72189C0E800151709 /* BrandableNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD824E62189C0E800151709 /* BrandableNavigationBar.swift */; };
......@@ -566,6 +572,9 @@
DD86BAF021EF5B6D004A988F /* UIViewAutolayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewAutolayout.swift; sourceTree = "<group>"; };
DD8BF3CA219C6BAA0041357C /* ConfirmVPNPlanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmVPNPlanViewController.swift; sourceTree = "<group>"; };
DDA4A7BD21F5C31400A02ACD /* IKEv2Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IKEv2Profile.swift; sourceTree = "<group>"; };
DDC0840C22EB06F400DA2701 /* Invites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Invites.swift; path = "../../../../../../../../../System/Volumes/Data/Users/ueshiba/Projects/PIA/client-library-apple/PIALibrary/Sources/Core/WebServices/Invites.swift"; sourceTree = "<group>"; };
DDC0840F22EB077400DA2701 /* InvitesInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InvitesInformation.swift; path = "../../../../../../../../../System/Volumes/Data/Users/ueshiba/Projects/PIA/client-library-apple/PIALibrary/Sources/Core/WebServices/InvitesInformation.swift"; sourceTree = "<group>"; };
DDC0841222EB07FB00DA2701 /* GlossInvitesInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GlossInvitesInformation.swift; path = "../../../../../../../../../System/Volumes/Data/Users/ueshiba/Projects/PIA/client-library-apple/PIALibrary/Sources/Library/WebServices/GlossInvitesInformation.swift"; sourceTree = "<group>"; };
DDC812472176166500CB290C /* SwiftGen+ScenesStoryboards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+ScenesStoryboards.swift"; sourceTree = "<group>"; };
DDC81249217617F900CB290C /* SwiftGen+SeguesStoryboards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+SeguesStoryboards.swift"; sourceTree = "<group>"; };
DDD824E22189969400151709 /* Preset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preset.swift; sourceTree = "<group>"; };
......@@ -732,6 +741,7 @@
0E3D13D81F9E273300434A48 /* GlossSignup.swift */,
DD58F4BA21AEB99C00D043F7 /* GlossToken.swift */,
DD56E3F3225F5D22002EDFB2 /* GlossProduct.swift */,
DDC0841222EB07FB00DA2701 /* GlossInvitesInformation.swift */,
0EFB512D1F82D7C50033B81F /* PIAWebServices.swift */,
0E392D9F1FE323ED0002160D /* PIAWebServices+Ephemeral.swift */,
0E392DA81FE329AD0002160D /* PlatformVPNLog.swift */,
......@@ -814,6 +824,8 @@
0EC849C81F82329F002480CA /* WebServices.swift */,
DD76292721ECDFF80092DF50 /* Usage.swift */,
DD56E3F5225F5D77002EDFB2 /* Product.swift */,
DDC0840F22EB077400DA2701 /* InvitesInformation.swift */,
DDC0840C22EB06F400DA2701 /* Invites.swift */,
);
path = WebServices;
sourceTree = "<group>";
......@@ -1623,6 +1635,7 @@
84125E09213D7E04001BCC19 /* PIAColors.swift in Sources */,
0E3D13D71F9E272B00434A48 /* GlossPayment.swift in Sources */,
0E2ADD491FE1595300BB170C /* IPSecProfile.swift in Sources */,
DDC0841422EB07FB00DA2701 /* GlossInvitesInformation.swift in Sources */,
0E7BC6F41F96B1120035C8B2 /* SecureStore.swift in Sources */,
0EFEB4C3200778BC00F81029 /* NSData+Compression.m in Sources */,
0EFEB4C520077AC900F81029 /* PIATunnelProfile.swift in Sources */,
......@@ -1634,6 +1647,7 @@
0E53A83F1FE5A4C8000C2A18 /* Client+Daemons.swift in Sources */,
0EFDC1CE1FE36D45007C0B9B /* Restylable.swift in Sources */,
0E392DA41FE3247E0002160D /* Endpoint.swift in Sources */,
DDC0840E22EB06F400DA2701 /* Invites.swift in Sources */,
0EE7710A1F9D21020029A77B /* Payment.swift in Sources */,
0E7BC6ED1F96B1040035C8B2 /* UserDefaultsStore.swift in Sources */,
DDFCFAB221E925BB0081F235 /* AvailableTiles.swift in Sources */,
......@@ -1657,6 +1671,7 @@
DDFCFAB421E925BB0081F235 /* TileableCell.swift in Sources */,
0E53A84E1FE5BB25000C2A18 /* VPNDaemon.swift in Sources */,
0EFEB4C420077AC900F81029 /* PIATunnelProvider+Profile.swift in Sources */,
DDC0841122EB077400DA2701 /* InvitesInformation.swift in Sources */,
DDFCFAB321E925BB0081F235 /* Tileable.swift in Sources */,
0E2ADCEA1FE0843000BB170C /* Macros+Pinger.swift in Sources */,
0E7BC6F71F96B1120035C8B2 /* WebServices.swift in Sources */,
......@@ -1716,10 +1731,12 @@
0E53A84A1FE5BA52000C2A18 /* Daemon.swift in Sources */,
0E492C641FE5F949007F23DF /* MockServerProvider.swift in Sources */,
0E53A8471FE5BA0B000C2A18 /* ServersDaemon.swift in Sources */,
DDC0840D22EB06F400DA2701 /* Invites.swift in Sources */,
0EE78AF81F818815002E4CDD /* AccountInfo.swift in Sources */,
DDFCFAAF21E925B60081F235 /* TileableCell.swift in Sources */,
DDFCFAB621E925F70081F235 /* EnumsBuilder.swift in Sources */,
0EFB512A1F82D45F0033B81F /* DefaultAccountProvider.swift in Sources */,
DDC0841022EB077400DA2701 /* InvitesInformation.swift in Sources */,
0E3D13D91F9E273300434A48 /* GlossSignup.swift in Sources */,
84577FBF213D8EE6006DEC3D /* ViewStyles.swift in Sources */,
0E0E5B0F1F8297C500022CD0 /* SecureStore.swift in Sources */,
......@@ -1783,6 +1800,7 @@
0E0E5B0D1F8297BD00022CD0 /* PlainStore.swift in Sources */,
0EE78AEE1F818720002E4CDD /* LibraryCallback.swift in Sources */,
0E53A8561FE5D770000C2A18 /* MockWebServices.swift in Sources */,
DDC0841322EB07FB00DA2701 /* GlossInvitesInformation.swift in Sources */,
0E392D7D1FE2E4C10002160D /* MemoryStore.swift in Sources */,
0E9D62DD1FDEE45A009A90CF /* DefaultServerProvider.swift in Sources */,
0EB3D9881FF06F37005B11F4 /* NetworkExtensionProfile.swift in Sources */,
......
......@@ -100,6 +100,24 @@ public protocol AccountProvider: class {
*/
func cleanDatabase()
/**
Fetch invites information.
- Precondition: `isLoggedIn` is `true`.
- Parameter callback: Returns the updated `InvitesInformation`.
*/
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?)
/**
Invite a user to subscribe using an e-mail.
- Precondition: `isLoggedIn` is `true`.
- Parameter name: The invitee name.
- Parameter email: The invitee email.
- Parameter callback: Returns `nil` on success.
*/
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?)
#if os(iOS)
/**
Lists the available plans with their corresponding product to purchase in order to get them.
......
//
// Invites.swift
// PIALibrary
//
// Created by Jose Antonio Blaya Garcia on 26/07/2019.
// Copyright © 2019 London Trust Media. All rights reserved.
//
import Foundation
/// The information associated with a `Invites`.
public struct Invites {
public let rewarded: Bool
public let accepted: Bool
public let obfuscatedEmail: String
public let gracePeriodRemaining: String
}
//
// InvitesInformation.swift
// PIALibrary
//
// Created by Jose Antonio Blaya Garcia on 26/07/2019.
// Copyright © 2019 London Trust Media. All rights reserved.
//
import Foundation
/// The information associated with a `InvitesInformation`.
public struct InvitesInformation {
public let totalInvitesSent: Int
public let totalInvitesRewarded: Int
public let totalFreeDaysGiven: Int
public let invites: [Invites]
}
......@@ -44,5 +44,9 @@ protocol WebServices: class {
func submitDebugLog(_ log: DebugLog, _ callback: SuccessLibraryCallback?)
// MARK: Friend Referral
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?)
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?)
}
......@@ -272,6 +272,22 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
callback?(nil)
}
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?) {
webServices.invitesInformation { (invitesInformation, error) in
guard let invitesInformation = invitesInformation else {
callback?(nil, error)
return
}
callback?(invitesInformation, nil)
}
}
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?) {
webServices.invite(name: name, email: email, { result in
callback?(result)
})
}
#if os(iOS)
func updatePlanProductIdentifiers(_ callback: LibraryCallback<[Product]>?) {
log.debug("Fetching available product keys...")
......
//
// GlossInvitesInformation.swift
// PIALibrary
//
// Created by Jose Antonio Blaya Garcia on 26/07/2019.
// Copyright © 2019 London Trust Media. All rights reserved.
//
import Foundation
import Gloss
class GlossInvitesInformation: GlossParser {
let parsed: InvitesInformation
required init?(json: JSON) {
let totalInvites: Int = "total_invites_sent" <~~ json ?? 0
let totalRewarded: Int = "total_invites_rewarded" <~~ json ?? 0
let totalDays: Int = "total_free_days_given" <~~ json ?? 0
var invites : [Invites] = []
if let inviteJSON = json["invites"] as? [JSON] {
for invite in inviteJSON {
guard let rewarded: Bool = "rewarded" <~~ invite else {
return nil
}
guard let accepted: Bool = "accepted" <~~ invite else {
return nil
}
guard let obfuscatedEmail: String = "obfuscated_email" <~~ invite else {
return nil
}
guard let gracePeriodRemaining: String = "grace_period_remaining" <~~ invite else {
return nil
}
invites.append(Invites(rewarded: rewarded,
accepted: accepted,
obfuscatedEmail: obfuscatedEmail,
gracePeriodRemaining: gracePeriodRemaining))
}
}
parsed = InvitesInformation(totalInvitesSent: totalInvites,
totalInvitesRewarded: totalRewarded,
totalFreeDaysGiven: totalDays,
invites: invites)
}
}
......@@ -247,6 +247,61 @@ class PIAWebServices: WebServices, ConfigurationAccess {
}
// MARK: Friend referral
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?) {
let endpoint = ClientEndpoint.invites
let status = [200, 400]
let errors: [Int: ClientError] = [
400: .badReceipt
]
req(nil, .get, endpoint, useAuthToken: true, nil, status, JSONRequestExecutor() { (json, status, error) in
if let knownError = self.knownError(endpoint, status, errors) {
callback?(nil, knownError)
return
}
guard let json = json else {
callback?(nil, error)
return
}
guard let invitesInformation = GlossInvitesInformation(json: json)?.parsed else {
callback?(nil, ClientError.malformedResponseData)
return
}
callback?(invitesInformation, nil)
})
}
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?) {
let endpoint = ClientEndpoint.invites
let status = [200, 400]
let errors: [Int: ClientError] = [
400: .badReceipt
]
let parameters = ["invitee_name": name,
"invitee_email": email]
req(nil, .post, endpoint, parameters, status, JSONRequestExecutor() { (json, status, error) in
if let knownError = self.knownError(endpoint, status, errors) {
callback?(knownError)
return
}
if let error = error {
callback?(error)
return
}
callback?(nil)
})
}
// MARK: Helpers
private func req(
......
......@@ -192,6 +192,19 @@ public class MockAccountProvider: AccountProvider, WebServicesConsumer {
delegate.cleanDatabase()
}
/// :nodoc:
public func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?) {
guard !mockIsUnauthorized else {
callback?(nil, ClientError.unauthorized)
return
}
delegate.invitesInformation(callback)
}
public func invite(name: String, email: String, _ callback: SuccessLibraryCallback?) {
delegate.invite(name: name, email: email, callback)
}
#if os(iOS)
/// :nodoc:
public func listPlanProducts(_ callback: (([Plan : InAppProduct]?, Error?) -> Void)?) {
......
......@@ -13,6 +13,8 @@ class MockWebServices: WebServices {
var accountInfo: (() -> AccountInfo)?
var invitesInformation: (() -> InvitesInformation)?
var serversBundle: (() -> ServersBundle)?
func token(credentials: Credentials, _ callback: ((String?, Error?) -> Void)?) {
......@@ -68,4 +70,15 @@ class MockWebServices: WebServices {
func planProductIdentifiers(_ callback: LibraryCallback<[Product]>?) {
callback?([Product(identifier: "com.product.monthly", plan: .monthly, price: "3.99", legacy: false)], nil)
}
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?) {
let result = invitesInformation?()
let error: ClientError? = (result == nil) ? .unsupported : nil
callback?(result, error)
}
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?) {
callback?(nil)
}
}
......@@ -327,9 +327,17 @@ class EphemeralAccountProvider: AccountProvider, ProvidersAccess, InAppAccess {
fatalError("Not implemented")
}
func invitesInformation(_ callback: LibraryCallback<InvitesInformation>?) {
fatalError("Not implemented")
}
func updatePlanProductIdentifiers(_ callback: LibraryCallback<[Product]>?) {
fatalError("Not implemented")
}
func invite(name: String, email: String, _ callback: SuccessLibraryCallback?) {
fatalError("Not implemented")
}
func listPlanProducts(_ callback: (([Plan : InAppProduct]?, Error?) -> Void)?) {
accessedProviders.accountProvider.listPlanProducts(callback)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment