aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Database/Realtime/FConnection.m
blob: 1550bfc0184b12de7a929b9b8d94b92c98c9e280 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "FConnection.h"
#import "FConstants.h"

typedef enum {
    REALTIME_STATE_CONNECTING = 0,
    REALTIME_STATE_CONNECTED = 1,
    REALTIME_STATE_DISCONNECTED = 2,
} FConnectionState;

@interface FConnection () {
    FConnectionState state;
}

@property (nonatomic, strong) FWebSocketConnection* conn;
@property (nonatomic, strong) FRepoInfo* repoInfo;

@end

#pragma mark -
#pragma mark FConnection implementation

@implementation FConnection

@synthesize delegate;
@synthesize conn;
@synthesize repoInfo;

#pragma mark -
#pragma mark Initializers

- (id)initWith:(FRepoInfo *)aRepoInfo andDispatchQueue:(dispatch_queue_t)queue lastSessionID:(NSString *)lastSessionID{
    self = [super init];
    if (self) {
        state = REALTIME_STATE_CONNECTING;
        self.repoInfo = aRepoInfo;
        self.conn = [[FWebSocketConnection alloc] initWith:self.repoInfo andQueue:queue lastSessionID:lastSessionID];
        self.conn.delegate = self;
    }
    return self;
}

#pragma mark -
#pragma mark Public method implementation

- (void)open {
    FFLog(@"I-RDB082001", @"Calling open in FConnection");
    [self.conn open];
}

- (void) closeWithReason:(FDisconnectReason)reason {
    if (state != REALTIME_STATE_DISCONNECTED) {
        FFLog(@"I-RDB082002", @"Closing realtime connection.");
        state = REALTIME_STATE_DISCONNECTED;
            
        if (self.conn) {
            FFLog(@"I-RDB082003", @"Calling close again.");
            [self.conn close];
            self.conn = nil;
        }
            
        [self.delegate onDisconnect:self withReason:reason];
    }
}

- (void) close {
    [self closeWithReason:DISCONNECT_REASON_OTHER];
}

- (void) sendRequest:(NSDictionary *)dataMsg sensitive:(BOOL)sensitive {
    // since this came from the persistent connection, wrap it in a data message envelope
    NSDictionary* msg = @{
        kFWPRequestType: kFWPRequestTypeData,
        kFWPRequestDataPayload: dataMsg
    };
    [self sendData:msg sensitive:sensitive];
}

#pragma mark -
#pragma mark Helpers


- (void) sendData:(NSDictionary *)data sensitive:(BOOL)sensitive {
    if (state != REALTIME_STATE_CONNECTED) {
        @throw [[NSException alloc] initWithName:@"InvalidConnectionState" reason:@"Tried to send data on an unconnected FConnection" userInfo:nil];
    } else {
        if (sensitive) {
            FFLog(@"I-RDB082004", @"Sending data (contents hidden)");
        } else {
            FFLog(@"I-RDB082005", @"Sending: %@", data);
        }
        [self.conn send:data];
    }
}

#pragma mark -
#pragma mark FWebSocketConnectinDelegate implementation

// Corresponds to onConnectionLost in JS
- (void)onDisconnect:(FWebSocketConnection *)fwebSocket wasEverConnected:(BOOL)everConnected {

    self.conn = nil;
    if (!everConnected && state == REALTIME_STATE_CONNECTING) {
        FFLog(@"I-RDB082006", @"Realtime connection failed.");

        // Since we failed to connect at all, clear any cached entry for this namespace in case the machine went away
        [self.repoInfo clearInternalHostCache];
    } else if (state == REALTIME_STATE_CONNECTED) {
        FFLog(@"I-RDB082007", @"Realtime connection lost.");
    }
    
    [self close];
}

// Corresponds to onMessageReceived in JS
- (void)onMessage:(FWebSocketConnection *)fwebSocket withMessage:(NSDictionary *)message {
    NSString* rawMessageType = [message objectForKey:kFWPAsyncServerEnvelopeType];
    if(rawMessageType != nil) {
        if([rawMessageType isEqualToString:kFWPAsyncServerDataMessage]) {
            [self onDataMessage:[message objectForKey:kFWPAsyncServerEnvelopeData]];
        }
        else if ([rawMessageType isEqualToString:kFWPAsyncServerControlMessage]) {
            [self onControl:[message objectForKey:kFWPAsyncServerEnvelopeData]];
        }
        else {
            FFLog(@"I-RDB082008", @"Unrecognized server packet type: %@", rawMessageType);    
        }
    }
    else {
        FFLog(@"I-RDB082009", @"Unrecognized raw server packet received: %@", message);
    }
}

- (void) onDataMessage:(NSDictionary *)message {
    // we don't do anything with data messages, just kick them up a level
    FFLog(@"I-RDB082010", @"Got data message: %@", message);
    [self.delegate onDataMessage:self withMessage:message];
}

- (void) onControl:(NSDictionary *)message {
    FFLog(@"I-RDB082011", @"Got control message: %@", message);
    NSString* type = [message objectForKey:kFWPAsyncServerControlMessageType];
    if([type isEqualToString:kFWPAsyncServerControlMessageShutdown]) {
        NSString* reason = [message objectForKey:kFWPAsyncServerControlMessageData];
        [self onConnectionShutdownWithReason:reason];
    }
    else if ([type isEqualToString:kFWPAsyncServerControlMessageReset]) {
        NSString* host = [message objectForKey:kFWPAsyncServerControlMessageData];
        [self onReset:host];
    }
    else if ([type isEqualToString:kFWPAsyncServerHello]) {
        NSDictionary* handshakeData = [message objectForKey:kFWPAsyncServerControlMessageData];
        [self onHandshake:handshakeData];
    }
    else {
        FFLog(@"I-RDB082012", @"Unknown control message returned from server: %@", message);
    }
}

- (void) onConnectionShutdownWithReason:(NSString *)reason {
    FFLog(@"I-RDB082013", @"Connection shutdown command received. Shutting down...");
    
    [self.delegate onKill:self withReason:reason];
    [self close];
}

- (void) onHandshake:(NSDictionary *)handshake {
    NSNumber* timestamp = [handshake objectForKey:kFWPAsyncServerHelloTimestamp];
//    NSString* version = [handshake objectForKey:kFWPAsyncServerHelloVersion];
    NSString* host = [handshake objectForKey:kFWPAsyncServerHelloConnectedHost];
    NSString* sessionID = [handshake objectForKey:kFWPAsyncServerHelloSession];
    
    self.repoInfo.internalHost = host;
        
    if (state == REALTIME_STATE_CONNECTING) {
        [self.conn start];
        [self onConnection:self.conn readyAtTime:timestamp sessionID:sessionID];
    }
}

- (void) onConnection:(FWebSocketConnection *)conn readyAtTime:(NSNumber *)timestamp sessionID:(NSString *)sessionID {
    FFLog(@"I-RDB082014", @"Realtime connection established");
    state = REALTIME_STATE_CONNECTED;
    
    [self.delegate onReady:self atTime:timestamp sessionID:sessionID];
}

- (void) onReset:(NSString *)host {
    FFLog(@"I-RDB082015", @"Got a reset; killing connection to: %@; Updating internalHost to: %@", repoInfo.internalHost, host);
    self.repoInfo.internalHost = host;

    // Explicitly close the connection with SERVER_RESET so calling code knows to reconnect immediately.
    [self closeWithReason:DISCONNECT_REASON_SERVER_RESET];
}

@end