New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[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
File filter...
Filter file types
Jump to file or symbol
Failed to load files and symbols.
+149 −66
Diff settings

Always

Just for now

@@ -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)
}
@@ -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)

This comment has been minimized.

@ashfurrow

ashfurrow Jun 6, 2016

Member

Oh gee, wish we had a linter! 😉

This comment has been minimized.

@orta

orta Jun 6, 2016

Member

hah


case "OperationFailedEvent": break
// TODO: Handle op failure

case "CommandSuccessful", "CommandFailed", "PostEventResponse":
postEventResponses.update(json)

@@ -79,4 +79,5 @@
#import "ARSerifStatusMaintainer.h"
#import "ARDeveloperOptions.h"

#import "ORStackView+ArtsyViews.h"
#import "ORStackView+ArtsyViews.h"
#import <CommonCrypto/CommonHMAC.h>

This comment has been minimized.

@ashfurrow

ashfurrow Jun 6, 2016

Member

Are these necessary anymore?

@@ -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
@@ -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 {

This comment has been minimized.

@ashfurrow

ashfurrow Jun 6, 2016

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?

This comment has been minimized.

@orta

orta Jun 6, 2016

Member

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

This comment has been minimized.

@ashfurrow

ashfurrow Jun 7, 2016

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
@@ -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() }
@@ -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

This comment has been minimized.

@ashfurrow

ashfurrow Jun 6, 2016

Member

This deserves a blog post too.

This comment has been minimized.

@briancroom

This comment has been minimized.

@ashfurrow
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? {
@@ -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
@@ -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:
@@ -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()
@@ -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") {
@@ -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)!
}
}
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.