aboutsummaryrefslogtreecommitdiff
path: root/lib/client/shardManager.ml
blob: 16aa5311bcebbd9e53b9ffee1b40b2f323a09cff (plain) (blame)
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
open Lwt.Infix
open Websocket

module Opcode = struct
    type t =
        | DISPATCH
        | HEARTBEAT
        | IDENTIFY
        | STATUS_UPDATE
        | VOICE_STATE_UPDATE
        | RESUME
        | RECONNECT
        | REQUEST_GUILD_MEMBERS
        | INVALID_SESSION
        | HELLO
        | HEARTBEAT_ACK

    let to_int = function
        | DISPATCH -> 0
        | HEARTBEAT -> 1
        | IDENTIFY -> 2
        | STATUS_UPDATE -> 3
        | VOICE_STATE_UPDATE -> 4
        | RESUME -> 6
        | RECONNECT -> 7
        | REQUEST_GUILD_MEMBERS -> 8
        | INVALID_SESSION -> 9
        | HELLO -> 10
        | HEARTBEAT_ACK -> 11

    let to_string = function
        | DISPATCH -> "DISPATCH"
        | HEARTBEAT -> "HEARTBEAT"
        | IDENTIFY -> "IDENTIFY"
        | STATUS_UPDATE -> "STATUS_UPDATE"
        | VOICE_STATE_UPDATE -> "VOICE_STATE_UPDATE"
        | RESUME -> "RESUME"
        | RECONNECT -> "RECONNECT"
        | REQUEST_GUILD_MEMBERS -> "REQUEST_GUILD_MEMBER"
        | INVALID_SESSION -> "INVALID_SESSION"
        | HELLO -> "HELLO"
        | HEARTBEAT_ACK -> "HEARTBEAT_ACK"
end

module Shard = struct
    type t = {
        send: (Frame.t -> unit Lwt.t);
        recv: (unit -> Frame.t Lwt. t);
        hb_interval: int;
        session_id: string;
        seq: int;
    }

    let connect uri =
        let url = Uri.to_string uri in
        let ip = Ipaddr.V4 Ipaddr.V4.any in
        Websocket_lwt.with_connection (`TLS (`Hostname url, `IP ip, `Port 443)) uri
        >|= fun (recv, send) ->
        (* Start heartbeat sequencing *)
        { send; recv; hb_interval = 42500; session_id = ""; seq = 0; }

    let send payload shard =
        payload
        |> shard.send
        |> ignore

    let wrap_payload d op =
        `Assoc [
            ("op", `Int op);
            ("d", d)
        ]

    let identify ?(threshold=250) shard id total token =
        let p = wrap_payload(`Assoc [
            ("token", `String token);
            ("properties", `Assoc [
                ("$os", `String Sys.os_type);
                ("$browser", `String "animus");
                ("$device", `String "animus");
            ]);
            ("large_threshold", `Int threshold);
            ("shard", `List [`Int id; `Int total]);
        ]) (Opcode.to_int IDENTIFY) in
        let p = Frame.create ~content:(Yojson.Basic.to_string p) () in
        send p shard

    let resume shard token =
        let p = wrap_payload (`Assoc [
            ("token", `String token);
            ("session_id", `String shard.session_id);
            ("seq", `Int shard.seq);
        ]) (Opcode.to_int RESUME) in
        let p = Frame.create ~content:(Yojson.Basic.to_string p) () in
        send p shard

    let heartbeat shard =
        let p = Frame.create () in
        send p shard
end

module ShardMap = Map.Make(
    struct
        type t = int
        let compare: int -> int -> int = Pervasives.compare
    end
)

type t = {
    shards: Shard.t ShardMap.t;
    gateway_url: Uri.t;
    token: string;
}

let create_shard manager =
    Shard.connect manager.gateway_url
    >|= fun shard ->
    let id = (ShardMap.cardinal manager.shards) + 1 in
    ShardMap.add id shard manager.shards

let identify manager id =
    let total = ShardMap.cardinal manager.shards in
    let shard = ShardMap.find id manager.shards in
    Shard.identify shard id total manager.token

let resume manager id =
    let shard = ShardMap.find id manager.shards in
    Shard.resume shard manager.token