[Live] Use the JWT to determine registration status instead of deriving it through eigen logic #1638

Merged
merged 7 commits into from Jun 7, 2016
+149 −66
Split
View
21 Artsy/Eigen.playground/Contents.swift
@@ -1 +1,22 @@
import Foundation
+
+let string = "eyJhdWQiOiJhdWN0aW9ucyIsInJvbGUiOiJiaWRkZXIiLCJ1c2VySWQiOiI0ZWM5MmNjYjU2YWU4ODAwMDEwMDAzOGUiLCJzYWxlSWQiOiI1NzU1YjA3YzVmOWY4ZjVjYjYwMDAwMDIiLCJiaWRkZXJJZCI6Ijk0MDIxNiIsImlhdCI6MTQ2NTI0MzkxMzIxMX0"
+
+let stringData = string.dataUsingEncoding(NSUTF8StringEncoding)
+
+//let decodedData = NSData(base64EncodedString: string, options:[.IgnoreUnknownCharacters])
+//let decodedData = NSData(base64EncodedData:stringData!, options:[.IgnoreUnknownCharacters])
+
+let paddedLength = string.characters.count + (4 - (string.characters.count % 4));
+let correctBase64String = string.stringByPaddingToLength(paddedLength, withString:"=", startingAtIndex:0)
+
+
+let decodedData = NSData(base64EncodedString: correctBase64String, options: [.IgnoreUnknownCharacters])
+
+
+
+//let decodedString = NSString(data: decodedData, encoding: NSUTF8StringEncoding)
+
+if let data = decodedData {
+ let result = NSString(data: data, encoding:NSUTF8StringEncoding)
+}
View
5 Artsy/Networking/Live_Auctions/LiveAuctionSocketCommunicator.swift
@@ -92,7 +92,7 @@ private extension SocketSetup {
func socketConnected() {
print ("Socket connected")
- socket.writeString("{\"type\":\"Authorize\",\"jwt\":\"\(jwt)\"}")
+ socket.writeString("{\"type\":\"Authorize\",\"jwt\":\"\(jwt.string)\"}")
socketConnectionSignal.update(true)
}
@@ -129,12 +129,11 @@ private extension SocketSetup {
updatedAuctionState.update(json)
case "LotUpdateBroadcast":
- lotUpdateBroadcasts.update(json)
+ lotUpdateBroadcasts.update(json)
@ashfurrow
ashfurrow Jun 6, 2016 Artsy member

Oh gee, wish we had a linter! 😉

@orta
orta Jun 6, 2016 Artsy member

hah

case "OperationFailedEvent": break
// TODO: Handle op failure
-
case "CommandSuccessful", "CommandFailed", "PostEventResponse":
postEventResponses.update(json)
View
3 Artsy/Resources/Artsy-Bridging-Header.h
@@ -79,4 +79,5 @@
#import "ARSerifStatusMaintainer.h"
#import "ARDeveloperOptions.h"
-#import "ORStackView+ArtsyViews.h"
+#import "ORStackView+ArtsyViews.h"
+#import <CommonCrypto/CommonHMAC.h>
@ashfurrow
ashfurrow Jun 6, 2016 Artsy member

Are these necessary anymore?

View
24 Artsy/View_Controllers/Live_Auctions/LiveAuctionLotViewController.swift
@@ -144,32 +144,22 @@ class LiveAuctionLotViewController: UIViewController {
// Subscribe to updates from our bidding view model, telling us what state the lot's bid status is in.
biddingViewModel.progressSignal.subscribe { [weak currentLotView, weak lotMetadataStack, weak historyViewController, weak self] bidState in
- let noCurrentLotExists: Bool
-
- switch self?.salesPerson.auctionViewModel.currentLotSignal.peek() {
- case .None: fallthrough
- case .Some(nil): noCurrentLotExists = true
- case .Some(_): noCurrentLotExists = false
- }
-
let hideCurrentLotCTA: Bool
let hideBidHistory: Bool
- switch bidState {
- case .Active:
- hideBidHistory = false
- hideCurrentLotCTA = true
- case .InActive where noCurrentLotExists:
- hideBidHistory = true
+ switch self?.salesPerson.auctionViewModel.currentLotSignal.peek() {
+ case .Some(let .Some(lot)):
+ let myLotID = self?.lotViewModel.lotID
+ hideCurrentLotCTA = (lot.lotID == myLotID)
+ hideBidHistory = !hideCurrentLotCTA
+
+ default: // a nil anywhere
hideCurrentLotCTA = true
- case .InActive:
hideBidHistory = true
- hideCurrentLotCTA = false
}
currentLotView?.hidden = hideCurrentLotCTA
- // Not sure this should stay this way, but things will have to change once we support dragging up the bid history anyway
historyViewController?.view.hidden = hideBidHistory
historyViewController?.tableView.scrollEnabled = hideBidHistory
pan.enabled = !hideBidHistory
View
22 Artsy/View_Controllers/Live_Auctions/LiveAuctionStateManager.swift
@@ -22,20 +22,12 @@ class LiveAuctionStateManager: NSObject {
let sale: LiveSale
let bidderID: String?
-
+ let bidderStatus: ArtsyAPISaleRegistrationStatus
private let socketCommunicator: LiveAuctionSocketCommunicatorType
private let stateReconciler: LiveAuctionStateReconcilerType
private var biddingStates = [String: LiveAuctionBiddingViewModelType]()
- var bidderStatus: ArtsyAPISaleRegistrationStatus {
- let loggedIn = User.currentUser() != nil
- let hasBidder = bidderID != nil
-
- if !loggedIn { return .NotLoggedIn }
- return hasBidder ? .Registered : .NotRegistered
- }
-
var socketConnectionSignal: Observable<Bool> {
return socketCommunicator.socketConnectionSignal
}
@@ -53,6 +45,8 @@ class LiveAuctionStateManager: NSObject {
self.socketCommunicator = socketCommunicatorCreator(host: host, causalitySaleID: sale.causalitySaleID, jwt: jwt)
self.stateReconciler = stateReconcilerCreator(saleArtworks: saleArtworks)
+ self.bidderStatus = LiveAuctionStateManager.registrationStatusFromJWT(jwt)
+
super.init()
socketCommunicator.updatedAuctionState.subscribe { [weak self] state in
@@ -80,6 +74,16 @@ class LiveAuctionStateManager: NSObject {
biddingViewModel?.bidPendingSignal.update(confirmed)
}
}
+
+ private class func registrationStatusFromJWT(jwt: JWT) -> ArtsyAPISaleRegistrationStatus {
@ashfurrow
ashfurrow Jun 6, 2016 Artsy member

It makes sense to have this in a class function, but I can see a few alternatives, like in an extension on JWT or as a free function outside the class. We (the community) are still in this really interesting period of Swift style experimentation, I think it's worth taking some notes during our pairing sessions this week and maybe coming up with a blog post?

@orta
orta Jun 6, 2016 Artsy member

aye it is, but realistically, this will get lost in the noise for the next few weeks from Live, then WWDC.

@ashfurrow
ashfurrow Jun 7, 2016 Artsy member

Hmm, better make a long-term OmniFocus project...

+ guard let _ = jwt.userID else { return .NotLoggedIn }
+ switch jwt.role {
+ case .Bidder:
+ return .Registered
+ default:
+ return .NotRegistered
+ }
+ }
}
private typealias PublicFunctions = LiveAuctionStateManager
View
43 Artsy/View_Controllers/Live_Auctions/LiveAuctionStateManagerSpec.swift
@@ -8,14 +8,16 @@ class LiveAuctionStateManagerSpec: QuickSpec {
override func spec() {
var subject: LiveAuctionStateManager!
var sale: LiveSale!
+ let stubbedJWT = ArtsyAPISaleRegistrationStatus.Registered.jwt
beforeEach {
OHHTTPStubs.stubJSONResponseForHost("metaphysics*.artsy.net", withResponse: [:])
// Not sure why ^ is needed, might be worth looking
sale = testLiveSale()
- subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: "abcdefg", bidderID: "bidder-id", socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
+
+ subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: stubbedJWT, bidderID: "bidder-id", socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
}
it("sets its saleID upon initialization") {
@@ -24,7 +26,7 @@ class LiveAuctionStateManagerSpec: QuickSpec {
it("creates an appropriate socket communicator") {
expect(mostRecentSocketCommunicator?.host) == "http://localhost"
- expect(mostRecentSocketCommunicator?.accessToken) == "abcdefg"
+ expect(mostRecentSocketCommunicator?.jwt.string) == stubbedJWT.string
expect(mostRecentSocketCommunicator?.causalitySaleID) == "some-random-string-of-nc72bjzj7"
}
@@ -52,33 +54,38 @@ class LiveAuctionStateManagerSpec: QuickSpec {
describe("bidderStatus") {
it("handles being logged out") {
- ARUserManager.clearUserData()
+ let jwt = ArtsyAPISaleRegistrationStatus.NotLoggedIn.jwt
+
+ subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: jwt, bidderID: "asdasd", socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
+
expect(subject.bidderStatus) == ArtsyAPISaleRegistrationStatus.NotLoggedIn
}
- it("handles being logged in and registered") {
- ARUserManager.asLoggedInUser {
+ it("handles being logged in and not registered") {
+
+ let jwt = ArtsyAPISaleRegistrationStatus.NotRegistered.jwt
- let bidderID: String? = nil
- subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: "abcdefg", bidderID: bidderID, socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
+ subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: jwt, bidderID: "asdasd", socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
+
+ expect(subject.bidderStatus) == ArtsyAPISaleRegistrationStatus.NotRegistered
- expect(subject.bidderStatus) == ArtsyAPISaleRegistrationStatus.NotRegistered
- }
}
it("handles being logged in and register") {
- ARUserManager.asLoggedInUser {
- expect(subject.bidderStatus) == ArtsyAPISaleRegistrationStatus.Registered
- }
+ let jwt = ArtsyAPISaleRegistrationStatus.Registered.jwt
+
+ subject = LiveAuctionStateManager(host: "http://localhost", sale: sale, saleArtworks: [], jwt: jwt, bidderID: nil, socketCommunicatorCreator: test_socketCommunicatorCreator(), stateReconcilerCreator: test_stateReconcilerCreator())
+
+ expect(subject.bidderStatus) == ArtsyAPISaleRegistrationStatus.Registered
}
}
}
}
func test_socketCommunicatorCreator() -> LiveAuctionStateManager.SocketCommunicatorCreator {
- return { host, saleID, accessToken in
- return Test_SocketCommunicator(host: host, causalitySaleID: saleID, accessToken: accessToken)
+ return { host, saleID, jwt in
+ return Test_SocketCommunicator(host: host, causalitySaleID: saleID, jwt: jwt)
}
}
@@ -94,12 +101,12 @@ class Test_SocketCommunicator: LiveAuctionSocketCommunicatorType {
let host: String
let causalitySaleID: String
- let accessToken: String
+ let jwt: JWT
- init(host: String, causalitySaleID: String, accessToken: String) {
+ init(host: String, causalitySaleID: String, jwt: JWT) {
self.host = host
self.causalitySaleID = causalitySaleID
- self.accessToken = accessToken
+ self.jwt = jwt
mostRecentSocketCommunicator = self
}
@@ -141,7 +148,7 @@ class Test_StateRecociler: LiveAuctionStateReconcilerType {
func processCurrentLotUpdate(update: AnyObject) {
mostRecentCurrentLotUpdate = update
}
-
+
var newLotsSignal: Observable<[LiveAuctionLotViewModelType]> { return Observable() }
var currentLotSignal: Observable<LiveAuctionLotViewModelType?> { return Observable() }
var saleSignal: Observable<LiveAuctionViewModelType> { return Observable() }
View
50 Artsy/View_Controllers/Live_Auctions/LiveAuctionStaticDataFetcher.swift
@@ -2,8 +2,50 @@ import Foundation
import Interstellar
import SwiftyJSON
+enum CausalityRole {
+ case Bidder
+ case Observer
+}
+
+class JWT {
+ let string: String
+ private let rawData: JSON
+
+ init?(jwtString: String) {
+ let components = jwtString.componentsSeparatedByString(".")
+ if components.count != 3 { return nil }
+
+ let body = components[1]
+
+ // From our side, this is more complex than you'd hope WRT base64 decoding a string
+ // for the full details: http://stackoverflow.com/a/21407393/385754
+
+ let paddedLength = body.characters.count + (4 - (body.characters.count % 4));
+ let correctBase64String = body.stringByPaddingToLength(paddedLength, withString:"=", startingAtIndex:0)
+
+ guard let decodedData = NSData(base64EncodedString: correctBase64String, options:[.IgnoreUnknownCharacters]),
+ let json = try? NSJSONSerialization.JSONObjectWithData(decodedData, options: [])
+ else { return nil}
+
+ rawData = JSON(json)
+ string = jwtString
+ }
+
+ var userID: String? {
+ return rawData["userId"].string
+ }
+
+ var role: CausalityRole {
+ switch rawData["role"].stringValue {
+ case "bidder":
+ return .Bidder
+ // Fallback for unknown roles
+ default:
+ return .Observer
+ }
+ }
+}
-typealias JWT = String
@ashfurrow
ashfurrow Jun 6, 2016 Artsy member

This deserves a blog post too.

@ashfurrow
ashfurrow Jun 25, 2016 Artsy member

🙇

typealias StaticSaleResult = Result<(sale: LiveSale, jwt: JWT, bidderID: String?)>
@@ -34,8 +76,8 @@ class LiveAuctionStaticDataFetcher: LiveAuctionStaticDataFetcherType {
return signal.update(.Error(Error.JSONParsing))
}
- guard let
- jwt = self.parseJWT(json) else {
+ guard
+ let jwt = self.parseJWT(json) else {
return signal.update(.Error(Error.NoJWTCredentials))
}
@@ -61,7 +103,7 @@ extension LiveAuctionStaticDataFetcherType {
}
func parseJWT(json: JSON) -> JWT? {
- return json["data"]["causality_jwt"].string
+ return JWT(jwtString: json["data"]["causality_jwt"].stringValue)
}
func parseBidderID(json: JSON) -> String? {
View
3 Artsy/View_Controllers/Live_Auctions/LiveAuctionViewController.swift
@@ -315,7 +315,8 @@ class Stubbed_StaticDataFetcher: LiveAuctionStaticDataFetcherType {
let sale = self.parseSale(JSON(json))!
let bidderID: String? = "awesome-bidder-id-aw-yeah"
- let s = (sale: sale, jwt: "", bidderID: bidderID)
+ let stubbedJWT = JWT(jwtString: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhdWN0aW9ucyIsInJvbGUiOiJvYnNlcnZlciIsInVzZXJJZCI6bnVsbCwic2FsZUlkIjoiNTRjN2U4ZmE3MjYxNjkyYjVhY2QwNjAwIiwiYmlkZGVySWQiOm51bGwsImlhdCI6MTQ2NTIzNDI2NDI2N30.2q3bh1E897walHdSXIocGKElbxOhCGmCCsL8Bf-UWNA")!
+ let s = (sale: sale, jwt: stubbedJWT, bidderID: bidderID)
signal.update(Result.Success(s))
return signal
View
4 Artsy/View_Controllers/Live_Auctions/Views/LiveAuctionBiddingViewModel.swift
@@ -64,8 +64,10 @@ class LiveAuctionBiddingViewModel: LiveAuctionBiddingViewModelType {
case .UpcomingLot:
if lotID == state.currentLot?.lotID {
return .Active(biddingState: .LotWaitingToOpen)
- } else {
+ } else if bidderStatus == .Registered {
return .InActive(lotState: state.lotState)
+ } else {
+ return .Active(biddingState: .TrialUser)
}
case .LiveLot:
View
6 Artsy_Tests/Networking_Tests/Live_Auctions/LiveAuctionSocketCommunicatorSpec.swift
@@ -9,7 +9,9 @@ var socket: Test_Socket!
class LiveAuctionSocketCommunicatorSpec: QuickSpec {
override func spec() {
let host = "squiggly host"
- let jwt = "123456"
+ let jwt = ArtsyAPISaleRegistrationStatus.Registered.jwt
+
+
let saleID = "honest ed's bargain basement"
beforeEach {
@@ -43,7 +45,7 @@ class LiveAuctionSocketCommunicatorSpec: QuickSpec {
socket.onConnect?()
- let authCalls = socket.writes.filter { $0 == "{\"type\":\"Authorize\",\"jwt\":\"\(jwt)\"}" }
+ let authCalls = socket.writes.filter { $0 == "{\"type\":\"Authorize\",\"jwt\":\"\(jwt.string)\"}" }
expect(authCalls).to( haveCount(1) )
_ = subject // Keep a reference around until after expect()
View
2 Artsy_Tests/Networking_Tests/Live_Auctions/LiveAuctionStaticDataFetcherSpec.swift
@@ -36,7 +36,7 @@ class LiveAuctionStaticDataFetcherSpec: QuickSpec {
}
it("fetches a jwt") {
- expect(receivedState.peekValue()?.jwt) == jwt
+ expect(receivedState.peekValue()?.jwt.string) == jwt
}
it("fetches a bidderId") {
View
BIN ...ceImages/LiveAuctionLotViewControllerTests/snapshots__looks_good_for_upcoming_lots@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN ...nLotViewControllerTests/snapshots__looks_good_when_its_lot_becomes_the_current_lot@2x.png
Deleted file not rendered
View
20 Artsy_Tests/View_Controller_Tests/Live_Auction/FakeSalesPerson.swift
@@ -9,5 +9,23 @@ func stub_auctionSale() -> LiveSale {
func stub_auctionSalesPerson() -> LiveAuctionsSalesPersonType {
let sale = stub_auctionSale()
- return LiveAuctionsSalesPerson(sale: sale, jwt: "abcdefg", bidderID: "bidder-id", stateManagerCreator: LiveAuctionsSalesPerson.stubbedStateManagerCreator())
+ return LiveAuctionsSalesPerson(sale: sale, jwt: ArtsyAPISaleRegistrationStatus.Registered.jwt , bidderID: "bidder-id", stateManagerCreator: LiveAuctionsSalesPerson.stubbedStateManagerCreator())
+}
+
+extension ArtsyAPISaleRegistrationStatus {
+ var jwt: JWT {
+ switch self {
+ case .Registered:
+ let loggedInRegistered = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhdWN0aW9ucyIsInJvbGUiOiJiaWRkZXIiLCJ1c2VySWQiOiI0ZWM5MmNjYjU2YWU4ODAwMDEwMDAzOGUiLCJzYWxlSWQiOiI1NzU1YjA3YzVmOWY4ZjVjYjYwMDAwMDIiLCJiaWRkZXJJZCI6Ijk0MDIxNiIsImlhdCI6MTQ2NTI0MzkxMzIxMX0.K3XuQ8n60Y5Co5YTxeY2VgqDhM3M_OIoUhGqrLlKbDw"
+ return JWT(jwtString: loggedInRegistered)!
+
+ case .NotRegistered:
+ let loggedInNotRegistered = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhdWN0aW9ucyIsInJvbGUiOiJvYnNlcnZlciIsInVzZXJJZCI6IjRlYzkyY2NiNTZhZTg4MDAwMTAwMDM4ZSIsInNhbGVJZCI6IjU3MWE1MmJmMjc1YjI0NTM0ZTAwMmRhNiIsImJpZGRlcklkIjpudWxsLCJpYXQiOjE0NjUyMzIzNTM1MzF9.EUBTm_QOIrEAZ_ulO2BrcZlmhUA28RLixiNvmdoLt74"
+ return JWT(jwtString: loggedInNotRegistered)!
+
+ case .NotLoggedIn:
+ let notLoggedIn = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhdWN0aW9ucyIsInJvbGUiOiJvYnNlcnZlciIsInVzZXJJZCI6bnVsbCwic2FsZUlkIjoiNTRjN2U4ZmE3MjYxNjkyYjVhY2QwNjAwIiwiYmlkZGVySWQiOm51bGwsImlhdCI6MTQ2NTI0NTQzOTAzN30.-UMb7YN3KUXYJPhTiHK2wv667bz4Lxj9cDMD6H9GcAw"
+ return JWT(jwtString: notLoggedIn)!
+ }
+ }
}
View
12 Artsy_Tests/View_Controller_Tests/Live_Auction/LiveAuctionLotViewControllerTests.swift
@@ -13,18 +13,19 @@ class LiveAuctionLotViewControllerTests: QuickSpec {
override func spec() {
describe("snapshots") {
+ // TODO: Lots on inconsistent state in here.
var subject: LiveAuctionLotViewController!
var auctionViewModel: Test_LiveAuctionViewModel!
var lotViewModel: Test_LiveAuctionLotViewModel!
+ var salesPerson: LiveAuctionsSalesPersonType!
beforeEach {
freezeTime()
auctionViewModel = Test_LiveAuctionViewModel()
auctionViewModel.saleAvailabilitySignal.update( .Active(liveAuctionDate: nil) )
lotViewModel = Test_LiveAuctionLotViewModel()
-
- let salesPerson = stub_auctionSalesPerson()
+ salesPerson = stub_auctionSalesPerson()
subject = LiveAuctionLotViewController(index: 1, lotViewModel: lotViewModel, salesPerson: salesPerson)
@@ -42,6 +43,7 @@ class LiveAuctionLotViewControllerTests: QuickSpec {
}
it("looks good for live lots") {
+ salesPerson.auctionViewModel.currentLotSignal.update(lotViewModel)
lotViewModel.lotStateSignal.update(.LiveLot)
expect(subject) == snapshot()
}
@@ -57,12 +59,6 @@ class LiveAuctionLotViewControllerTests: QuickSpec {
auctionViewModel.saleAvailabilitySignal.update(.Closed)
expect(subject) == snapshot()
}
-
- it("looks good when its lot becomes the current lot") {
- lotViewModel.lotStateSignal.update(.LiveLot)
- auctionViewModel.currentLotSignal.update(lotViewModel)
- expect(subject) == snapshot()
- }
}
}