File size: 2,931 Bytes
5c05829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { DEFAULT_TIMEOUT } from '../lib/constants'
import type RealtimeChannel from '../RealtimeChannel'

export default class Push {
  sent: boolean = false
  timeoutTimer: number | undefined = undefined
  ref: string = ''
  receivedResp: {
    status: string
    response: { [key: string]: any }
  } | null = null
  recHooks: {
    status: string
    callback: Function
  }[] = []
  refEvent: string | null = null

  /**
   * Initializes the Push
   *
   * @param channel The Channel
   * @param event The event, for example `"phx_join"`
   * @param payload The payload, for example `{user_id: 123}`
   * @param timeout The push timeout in milliseconds
   */
  constructor(
    public channel: RealtimeChannel,
    public event: string,
    public payload: { [key: string]: any } = {},
    public timeout: number = DEFAULT_TIMEOUT
  ) {}

  resend(timeout: number) {
    this.timeout = timeout
    this._cancelRefEvent()
    this.ref = ''
    this.refEvent = null
    this.receivedResp = null
    this.sent = false
    this.send()
  }

  send() {
    if (this._hasReceived('timeout')) {
      return
    }
    this.startTimeout()
    this.sent = true
    this.channel.socket.push({
      topic: this.channel.topic,
      event: this.event,
      payload: this.payload,
      ref: this.ref,
      join_ref: this.channel._joinRef(),
    })
  }

  updatePayload(payload: { [key: string]: any }): void {
    this.payload = { ...this.payload, ...payload }
  }

  receive(status: string, callback: Function) {
    if (this._hasReceived(status)) {
      callback(this.receivedResp?.response)
    }

    this.recHooks.push({ status, callback })
    return this
  }

  startTimeout() {
    if (this.timeoutTimer) {
      return
    }
    this.ref = this.channel.socket._makeRef()
    this.refEvent = this.channel._replyEventName(this.ref)

    const callback = (payload: any) => {
      this._cancelRefEvent()
      this._cancelTimeout()
      this.receivedResp = payload
      this._matchReceive(payload)
    }

    this.channel._on(this.refEvent, {}, callback)

    this.timeoutTimer = <any>setTimeout(() => {
      this.trigger('timeout', {})
    }, this.timeout)
  }

  trigger(status: string, response: any) {
    if (this.refEvent)
      this.channel._trigger(this.refEvent, { status, response })
  }

  destroy() {
    this._cancelRefEvent()
    this._cancelTimeout()
  }

  private _cancelRefEvent() {
    if (!this.refEvent) {
      return
    }

    this.channel._off(this.refEvent, {})
  }

  private _cancelTimeout() {
    clearTimeout(this.timeoutTimer)
    this.timeoutTimer = undefined
  }

  private _matchReceive({
    status,
    response,
  }: {
    status: string
    response: Function
  }) {
    this.recHooks
      .filter((h) => h.status === status)
      .forEach((h) => h.callback(response))
  }

  private _hasReceived(status: string) {
    return this.receivedResp && this.receivedResp.status === status
  }
}