p2p_streams_channel 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/README.md +210 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/javascripts/p2p/index.js +6 -0
  6. data/app/assets/javascripts/p2p/message.js +22 -0
  7. data/app/assets/javascripts/p2p/p2p_connection.js +133 -0
  8. data/app/assets/javascripts/p2p/p2p_controller.js +52 -0
  9. data/app/assets/javascripts/p2p/p2p_frame.js +152 -0
  10. data/app/assets/javascripts/p2p/p2p_peer.js +208 -0
  11. data/app/assets/javascripts/p2p/package.json +14 -0
  12. data/app/channels/signaling_channel.rb +29 -0
  13. data/app/helpers/p2p_streams_channel/tag_helper.rb +16 -0
  14. data/lib/p2p_streams_channel/cache.rb +22 -0
  15. data/lib/p2p_streams_channel/engine.rb +20 -0
  16. data/lib/p2p_streams_channel/negotiation.rb +25 -0
  17. data/lib/p2p_streams_channel/session.rb +57 -0
  18. data/lib/p2p_streams_channel/session_handler.rb +32 -0
  19. data/lib/p2p_streams_channel/session_state.rb +33 -0
  20. data/lib/p2p_streams_channel/version.rb +5 -0
  21. data/lib/p2p_streams_channel.rb +29 -0
  22. data/lib/rails/generators/p2p_streams_channel/controller_generator.rb +12 -0
  23. data/lib/rails/generators/p2p_streams_channel/install_generator.rb +24 -0
  24. data/lib/rails/generators/p2p_streams_channel/templates/controller.js +51 -0
  25. data/lib/rails/generators/p2p_streams_channel/templates/initializer.rb +4 -0
  26. data/p2p_streams_channel.gemspec +38 -0
  27. data/sig/p2p_streams_channel.rbs +4 -0
  28. data/spec/dummy/Rakefile +6 -0
  29. data/spec/dummy/app/assets/config/manifest.js +4 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  31. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  32. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  33. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  34. data/spec/dummy/app/controllers/p2p_controller.rb +7 -0
  35. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  36. data/spec/dummy/app/helpers/p2p_helper.rb +2 -0
  37. data/spec/dummy/app/javascript/application.js +4 -0
  38. data/spec/dummy/app/javascript/controllers/application.js +9 -0
  39. data/spec/dummy/app/javascript/controllers/chat_controller.js +46 -0
  40. data/spec/dummy/app/javascript/controllers/index.js +11 -0
  41. data/spec/dummy/app/jobs/application_job.rb +7 -0
  42. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  43. data/spec/dummy/app/models/application_record.rb +3 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +16 -0
  45. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  46. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  47. data/spec/dummy/app/views/p2p/index.html.erb +6 -0
  48. data/spec/dummy/app/views/p2p/room_chat.html.erb +19 -0
  49. data/spec/dummy/bin/importmap +4 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/bin/setup +33 -0
  53. data/spec/dummy/config/application.rb +22 -0
  54. data/spec/dummy/config/boot.rb +5 -0
  55. data/spec/dummy/config/cable.yml +12 -0
  56. data/spec/dummy/config/database.yml +25 -0
  57. data/spec/dummy/config/environment.rb +5 -0
  58. data/spec/dummy/config/environments/development.rb +68 -0
  59. data/spec/dummy/config/environments/production.rb +87 -0
  60. data/spec/dummy/config/environments/test.rb +60 -0
  61. data/spec/dummy/config/importmap.rb +7 -0
  62. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  63. data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  64. data/spec/dummy/config/initializers/inflections.rb +16 -0
  65. data/spec/dummy/config/initializers/p2p_streams_channel.rb +4 -0
  66. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  67. data/spec/dummy/config/locales/en.yml +33 -0
  68. data/spec/dummy/config/puma.rb +43 -0
  69. data/spec/dummy/config/routes.rb +4 -0
  70. data/spec/dummy/config/storage.yml +34 -0
  71. data/spec/dummy/config.ru +6 -0
  72. data/spec/dummy/db/test.sqlite3 +0 -0
  73. data/spec/dummy/log/test.log +0 -0
  74. data/spec/dummy/public/404.html +67 -0
  75. data/spec/dummy/public/422.html +67 -0
  76. data/spec/dummy/public/500.html +66 -0
  77. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  78. data/spec/dummy/public/apple-touch-icon.png +0 -0
  79. data/spec/dummy/public/favicon.ico +0 -0
  80. data/spec/dummy/tmp/development_secret.txt +1 -0
  81. data/spec/p2p_peer_status_spec.rb +89 -0
  82. data/spec/p2p_send_data_spec.rb +52 -0
  83. data/spec/p2p_streams_channel_spec.rb +7 -0
  84. data/spec/rails_helper.rb +8 -0
  85. data/spec/session_spec.rb +29 -0
  86. data/spec/spec_helper.rb +16 -0
  87. metadata +259 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8bda1bfc27255440985ebe30c675e2177ca08bf446b75d6ae9a7cfa5c619c59
4
+ data.tar.gz: 4fd9269cf47645dff407538bb4a817c490623bed92b63f3a691c71c1ea8159c1
5
+ SHA512:
6
+ metadata.gz: 7df5626c1517a3a18a8d495a2ac2d22bdbd1e7fe3c3f04a87b0ff02eed8b33c9c9c3a3f683b14c86ad157960ce2bb8f058fdd841c6ece438115bc6c098f01020
7
+ data.tar.gz: '082a674eb465701535db76a9d4772658f4bef2dc7b7246e2628ad1afd7af55731259c9041b0e58c9b13840b6f823cd07a4037f9ffa9b85c785285d3b78eaad29'
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # P2pStreamsChannel
2
+
3
+ Allow to setup one-to-many P2P stream connections ([WebRTC](https://webrtc.org/)) between clients through Rails server (ActionCable) as the signaling server.
4
+
5
+ ## Work Flow
6
+
7
+
8
+ ```
9
+ ===================================
10
+ Connect to Signaling Server (Rails)
11
+ ===================================
12
+
13
+ host-user -------------------------------------> Rails server
14
+ host-user <-----views/chat_room----------------- Rails server
15
+ (session: chat_room, peer_id: host_user)
16
+ host-user ---turbo-connect-stream--------------> Rails server/ActionCable
17
+
18
+ client-user -----------------------------------> Rails server
19
+ client-user <-----views/chat_room--------------- Rails server
20
+ (session: chat_room, peer_id: client_user)
21
+ client-user ---turbo-connect-stream------------> Rails server/ActionCable
22
+
23
+ ...
24
+ other clients
25
+ ...
26
+
27
+
28
+ ===========
29
+ Negotiation
30
+ ===========
31
+
32
+ host-user ------SessionReady--> Rails Server --> client-user
33
+ client-user ----SessionReady--> Rails Server --> host-user
34
+ host-user --setupRTCPeerConnection --+
35
+ <-----------------------------+
36
+
37
+ host-user ------SdpOffer -----> Rails Server --> client-user
38
+ client-user --setupRTCPeerConnection --+
39
+ <------------------------------+
40
+
41
+ client-user ----SdpAnswer ----> Rails Server --> host-user
42
+
43
+ iceServers --ice-candidate----> host-user ----> client-user
44
+ iceServers --ice-candidate----> client-user --> host-user
45
+
46
+
47
+ =========
48
+ Connected
49
+ =========
50
+
51
+ After client-user connected to the host-user, all of them will disconnected to the signaling server (in order to save memory and other things), only the host-user keep connect to Rails server.
52
+ In case you want keep client connection, you could set params `keepCableConnection: true` to the p2p-frame.
53
+
54
+ client-user1 ----X disconnect from -----> Rails server Action cable
55
+ client-user2 ----X disconnect from -----> Rails server Action cable
56
+ ...
57
+ host-user <-------keep connect to ------> Rails server Action cable
58
+
59
+ This is one-to-many p2p connections:
60
+ client-user1 --- send message 'hi' ---> host-user
61
+ host-user --- send message {user1: 'hi'} to --> others
62
+
63
+
64
+ ============
65
+ Disconnected
66
+ ============
67
+
68
+ client-user1 ---X disconnect ---> host-user
69
+ host-user ---> send client-user1 status ---> others
70
+
71
+ client-user1 ----reload --------> Rails server
72
+ host-user <--- start re-negotiating through Rails server ----> client-user1
73
+
74
+
75
+ host-user ---X disconnect ---> Rails server
76
+ all client-users will be disconnected
77
+ the first client-user re-connect to Rails server will become the new host
78
+ and start work-flow again
79
+
80
+ ```
81
+
82
+ ## Installation
83
+
84
+ ```ruby
85
+ $ gem "p2p_streams_channel"
86
+ $ bundle isntall
87
+ $ rails g p2p_streams_channel:install
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ Create a Stimulus P2pController in which you will receive other p2p-connections status, data send by other connected connections, and send your data to others.
93
+ ```ruby
94
+ $ rails g p2p_streams_channel:controller chat
95
+ # it will create js file `app/javascript/controllers/chat_controller.js`
96
+ ```
97
+
98
+ Render a p2p-frame-tag
99
+ ```ruby
100
+ # views/chat_rooms/_chat_room.html.erb
101
+ <%= p2p_frame_tag(
102
+ session_id: dom_id(chat_room),
103
+ peer_id: dom_id(current_user),
104
+ # config: {
105
+ # ice_servers: [
106
+ # { urls: ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] },
107
+ # ],
108
+ # heartbeat: {
109
+ # interval_mls: 100,
110
+ # idle_timeout_mls: 200
111
+ # },
112
+ # keepCableConnection: false
113
+ # }
114
+ ) do %>
115
+ <div data-controller="chat">
116
+ # chat room views
117
+ </div>
118
+ <% end %>
119
+ ```
120
+
121
+
122
+ Now you could implement your client side,
123
+ for example: here is a simple chat controller:
124
+
125
+
126
+ ```js
127
+ // app/javascript/controllers/chat_controller.js
128
+ import { P2pController } from "p2p"
129
+ export default class extends P2pController {
130
+ //
131
+ // p2p callbacks
132
+ //
133
+ p2pNegotiating() {
134
+ // your peer start to negotiate with the host through ActionCable
135
+ console.log("NEGOTIATING ...")
136
+ this.showConnecting()
137
+ }
138
+
139
+ p2pConnecting() {
140
+ // your peer is connecting directly with the host peer
141
+ console.log("CONNECTING ...")
142
+ }
143
+
144
+ p2pConnected() {
145
+ // your peer's connected to the host peer
146
+ // from now you could start send message to the other through the host peer
147
+ console.log("CONNECTED ...")
148
+ this.hideConnecting()
149
+ this.showChatBox()
150
+ }
151
+
152
+ p2pDisconnected() {
153
+ // your peer's disconnected from the host peer
154
+ this.showConnecting()
155
+ }
156
+
157
+ p2pClosed() {}
158
+
159
+ p2pError() {}
160
+
161
+ // receiving message from the other through the host peer
162
+ p2pReceivedMessage(message) {
163
+ switch(message["type"]) {
164
+ case "Data":
165
+ // message["data"]: the text message
166
+ const chatLine = document.createElement("div")
167
+ chatLine.innerText = `${message["senderId"]}: ${message["data"]}`
168
+ this.chatBoxTarget.append(chatLine)
169
+ break
170
+ case "Data.Connection.State":
171
+ // message["data"]: the current connection state of other peers
172
+ for (let [peer, state] of Object.entries(message["data"])) {
173
+ this.updatePeerState(peer, state)
174
+ }
175
+ break
176
+ default:
177
+ break
178
+ }
179
+ }
180
+
181
+ //
182
+ // send message to the others through the host peer:
183
+ // for example:
184
+ // send-button in message box will trigger this action
185
+ send() {
186
+ this.p2pSendMessage(this.messageBoxTarget.value)
187
+ this.messageBoxTarget.value = ""
188
+ }
189
+
190
+ // others:
191
+ // this.iamHost: your peer is the host or not
192
+ // this.peerId: your peer id
193
+ // this.hostPeerId: the host peer id
194
+ //
195
+ }
196
+ ```
197
+
198
+ ## Development
199
+
200
+ run test:
201
+ ```ruby
202
+ $ cd spec/dummy
203
+ $ rails g p2p_streams_channel:install
204
+ $ cd ../..
205
+ $ rake spec
206
+ ```
207
+
208
+ ## Contributing
209
+
210
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/p2p_streams_channel.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,6 @@
1
+ import "p2p/p2p_frame"
2
+ import P2pController from "p2p/p2p_controller"
3
+
4
+ export {
5
+ P2pController
6
+ }
@@ -0,0 +1,22 @@
1
+ export const ConnectionState = {
2
+ SessionJoin: "SessionJoin",
3
+ SessionReady: "SessionReady",
4
+ SdpOffer: "SdpOffer",
5
+ SdpAnswer: "SdpAnswer",
6
+ IceCandidate: "IceCandidate",
7
+ Error: "Error",
8
+ New: "new",
9
+ Negotiating: "negotiating",
10
+ Connecting: "connecting",
11
+ Connected: "connected",
12
+ DisConnected: "disconnected",
13
+ Closed: "closed",
14
+ Failed: "failed",
15
+ }
16
+
17
+ export const MessageType = {
18
+ Connection: "Connection",
19
+ Heartbeat: "Heartbeat",
20
+ Data: "Data",
21
+ DataConnectionState: "Data.Connection.State",
22
+ }
@@ -0,0 +1,133 @@
1
+ import { ConnectionState, MessageType } from "p2p/message"
2
+
3
+ const ICE_CONFIG = {
4
+ iceServers: [
5
+ {
6
+ urls: ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"]
7
+ },
8
+ ],
9
+ }
10
+
11
+ export default class P2pConnection {
12
+ constructor(peer, clientId, hostId, iamHost, iceConfig, heartbeatConfig) { // TODO: add heartbeatInterval to config
13
+ this.peer = peer
14
+ this.clientId = clientId
15
+ this.hostId = hostId
16
+ this.iamHost = iamHost
17
+ this.state = ConnectionState.New
18
+ this.lastTimeUpdate = 0
19
+ this.iceConfig = iceConfig || ICE_CONFIG
20
+ this.heartbeatConfig = heartbeatConfig
21
+ }
22
+
23
+ setupRTCPeerConnection() {
24
+ // console.log("connection start ...")
25
+ this.rtcPeerConnection = new RTCPeerConnection(this.iceConfig)
26
+
27
+ this.rtcPeerConnection.onicecandidate = event => {
28
+ // console.log(event)
29
+ if (event.candidate) {
30
+ let ice = {}
31
+ ice[ConnectionState.IceCandidate] = event.candidate
32
+ this.peer.signal(ConnectionState.IceCandidate, ice)
33
+ }
34
+ }
35
+ this.rtcPeerConnection.oniceconnectionstatechange = event => {
36
+ // console.log(event)
37
+ }
38
+
39
+ this.rtcPeerConnection.onconnectionstatechange = (ev) => {
40
+ // console.log(`onconnectionstatechange ${this.rtcPeerConnection.connectionState}`)
41
+ this.state = this.rtcPeerConnection.connectionState
42
+ if (this.state == ConnectionState.DisConnected || this.state == ConnectionState.Closed) {
43
+ this.close()
44
+ }
45
+ this.peer.updateP2pConnectionState(this)
46
+ }
47
+
48
+ this.sendDataChannel = this.rtcPeerConnection.createDataChannel("sendChannel")
49
+ this.sendDataChannelOpen = false
50
+ this.sendDataChannel.onopen = this.handleSendChannelStatusChange.bind(this)
51
+ this.sendDataChannel.onclose = this.handleSendChannelStatusChange.bind(this)
52
+
53
+ this.rtcPeerConnection.ondatachannel = event => {
54
+ // console.log("ondatachannel p2p ...")
55
+ this.receiveDataChannel = event.channel
56
+ this.receiveDataChannel.onmessage = this.receiveP2pMessage.bind(this)
57
+ this.receiveDataChannel.onopen = this.handleReceiveChannelStatusChange.bind(this)
58
+ this.receiveDataChannel.onclose = this.handleReceiveChannelStatusChange.bind(this)
59
+
60
+ this.peer.updateP2pConnectionState(this)
61
+ }
62
+
63
+ return this.rtcPeerConnection
64
+ }
65
+
66
+ receiveP2pMessage(event) {
67
+ // console.log(`p2p received msg: ${event.data}`)
68
+ const msg = JSON.parse(event.data)
69
+ switch (msg.type) {
70
+ case MessageType.Heartbeat:
71
+ this.state = ConnectionState.Connected
72
+ this.lastTimeUpdate = Date.now()
73
+ break
74
+ default:
75
+ this.peer.receivedP2pMessage(msg)
76
+ break
77
+ }
78
+ }
79
+
80
+ sendP2pMessage(message, type = MessageType.Data, senderId = null) {
81
+ if (this.sendDataChannel && this.sendDataChannelOpen) {
82
+ const msgJson = JSON.stringify({
83
+ type: type,
84
+ senderId: senderId || this.peer.peerId,
85
+ data: message
86
+ })
87
+ this.sendDataChannel.send(msgJson)
88
+ } else {
89
+ // console.warn("the send data channel is not available!")
90
+ }
91
+ }
92
+
93
+ handleSendChannelStatusChange(event) {
94
+ // console.log(event)
95
+ if (this.sendDataChannel) {
96
+ this.sendDataChannelOpen = this.sendDataChannel.readyState == "open"
97
+ if (this.sendDataChannelOpen && this.heartbeatConfig) {
98
+ this.scheduleHeartbeat()
99
+ }
100
+ }
101
+ }
102
+
103
+ handleReceiveChannelStatusChange(event) {
104
+ // console.log(event)
105
+ }
106
+
107
+ scheduleHeartbeat() {
108
+ this.heartbeat = setTimeout(() => {
109
+ this.sendHeartbeat()
110
+ }, this.heartbeatConfig.interval_mls)
111
+ }
112
+
113
+ sendHeartbeat() {
114
+ if (this.lastTimeUpdate > 0 && Date.now() - this.lastTimeUpdate > this.heartbeatConfig.idle_timeout_mls) {
115
+ // console.log("HEART-BEAT DETECT DISCONNECTED ............")
116
+ this.state = ConnectionState.DisConnected
117
+ this.peer.updateP2pConnectionState(this)
118
+ } else {
119
+ this.sendP2pMessage("ping", MessageType.Heartbeat)
120
+ this.scheduleHeartbeat()
121
+ }
122
+ }
123
+
124
+ stopHeartbeat() {
125
+ // console.log(`stop heartbeat ${this.hostId} <-> ${this.clientId}`)
126
+ clearTimeout(this.heartbeat)
127
+ }
128
+
129
+ close() {
130
+ // console.log(`close the connection ${this.hostId} <-> ${this.clientId}`)
131
+ this.stopHeartbeat()
132
+ }
133
+ }
@@ -0,0 +1,52 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.p2pSetup()
6
+ }
7
+
8
+ p2pSetup() {
9
+ this.p2pFrame = this.element.closest("p2p-frame")
10
+ if (this.p2pFrame) {
11
+ this.p2pFrame.setP2pListener(this)
12
+ } else {
13
+ throw new Error("Couldn't find p2p-frame!")
14
+ }
15
+ }
16
+
17
+ get peerId() {
18
+ this.p2pFrame.peer?.peerId
19
+ }
20
+
21
+ get hostPeerId() {
22
+ this.p2pFrame.peer?.hostPeerId
23
+ }
24
+
25
+ get iamHost() {
26
+ this.p2pFrame.peer?.iamHost
27
+ }
28
+
29
+ // p2p callbacks
30
+
31
+ p2pNegotiating() {}
32
+
33
+ p2pConnecting() {}
34
+
35
+ p2pConnected() {}
36
+
37
+ p2pDisconnected() {}
38
+
39
+ p2pClosed() {}
40
+
41
+ p2pError() {}
42
+
43
+ // send/received p2p message
44
+
45
+ p2pSendMessage(message) {
46
+ if (this.p2pFrame) {
47
+ this.p2pFrame.sendP2pMessage(message)
48
+ }
49
+ }
50
+
51
+ p2pReceivedMessage(message) {}
52
+ }
@@ -0,0 +1,152 @@
1
+ import { Turbo, cable } from "@hotwired/turbo-rails"
2
+ import P2pPeer from "p2p/p2p_peer"
3
+ import { MessageType } from "p2p/message"
4
+
5
+ class P2pFrameElement extends HTMLElement {
6
+ constructor() {
7
+ super()
8
+ this.listeners ||= []
9
+ }
10
+
11
+ // called each time the element is added to the document.
12
+ async connectedCallback() {
13
+ Turbo.connectStreamSource(this)
14
+ this.subscription = await cable.subscribeTo(this.channel, {
15
+ received: this.receiveSignal.bind(this),
16
+ connected: this.subscriptionConnected.bind(this),
17
+ disconnected: this.subscriptionDisconnected.bind(this)
18
+ }).catch(err => console.log(err))
19
+
20
+ this.peer = new P2pPeer(this.sessionId, this.peerId, this, this.subscription, this.iceConfig, this.heartbeatConfig)
21
+ }
22
+
23
+ // called each time the element is removed from the document.
24
+ disconnectedCallback() {
25
+ // console.log("p2p-frame disconnected")
26
+ this.unsubscribeSignalChannel()
27
+ }
28
+
29
+ subscriptionConnected() {
30
+ // console.log("subscriptionConnected")
31
+ this.peer.setup()
32
+ }
33
+
34
+ subscriptionDisconnected() {
35
+ // console.log("subscriptionDisconnected")
36
+ }
37
+
38
+ receiveSignal(message) {
39
+ // console.log("receive signal")
40
+ // console.log(message)
41
+ if (message.type == MessageType.Connection) {
42
+ this.peer.negotiate(message)
43
+ }
44
+ }
45
+
46
+ setP2pListener(listener) {
47
+ this.listeners ||= []
48
+ this.listeners.push(listener)
49
+ }
50
+
51
+ dispatchP2pMessage(message) {
52
+ this.listeners.forEach(listener => {
53
+ listener.p2pReceivedMessage(message)
54
+ })
55
+ }
56
+
57
+ sendP2pMessage(msg) {
58
+ this.peer.sendP2pMessage(msg)
59
+ }
60
+
61
+ p2pNegotiating() {
62
+ this.listeners.forEach(listener => {
63
+ listener.p2pNegotiating()
64
+ })
65
+ }
66
+
67
+ p2pConnecting() {
68
+ this.listeners.forEach(listener => {
69
+ listener.p2pConnecting()
70
+ })
71
+ }
72
+
73
+ p2pConnected() {
74
+ this.listeners.forEach(listener => {
75
+ listener.p2pConnected()
76
+ })
77
+
78
+ // only host-peer retain connect to the signal server
79
+ if (!this.peer?.iamHost && !this.keepCableConnection) {
80
+ // console.log("im not host so unsubscribe")
81
+ this.unsubscribeSignalChannel()
82
+ }
83
+ }
84
+
85
+ p2pDisconnected() {
86
+ this.listeners.forEach(listener => {
87
+ listener.p2pDisconnected()
88
+ })
89
+ }
90
+
91
+ p2pClosed() {
92
+ this.listeners.forEach(listener => {
93
+ listener.p2pClosed()
94
+ })
95
+ }
96
+
97
+ p2pError() {
98
+ this.listeners.forEach(listener => {
99
+ listener.p2pError()
100
+ })
101
+ }
102
+
103
+ async unsubscribeSignalChannel() { // TODO: MAKE SURE `SignalingChannel stopped streaming`
104
+ if (this.subscription) this.subscription.unsubscribe()
105
+ Turbo.disconnectStreamSource(this)
106
+ let consumer = await cable.getConsumer()
107
+ if (consumer) consumer.disconnect()
108
+ }
109
+
110
+ get sessionId() {
111
+ return this.getAttribute("session-id")
112
+ }
113
+
114
+ get peerId() {
115
+ return this.getAttribute("peer-id")
116
+ }
117
+
118
+ get params() {
119
+ return JSON.parse(this.getAttribute("params"))
120
+ }
121
+
122
+ get config() {
123
+ return this.params["config"] || {}
124
+ }
125
+
126
+ get iceConfig() {
127
+ return {
128
+ iceServers: this.config["ice_servers"]
129
+ }
130
+ }
131
+
132
+ get heartbeatConfig() {
133
+ return this.config["heartbeat"]
134
+ }
135
+
136
+ get keepCableConnection() {
137
+ return this.config["keep_cable_connection"]
138
+ }
139
+
140
+ get channel() {
141
+ const channel = this.getAttribute("channel")
142
+ const signed_stream_name = this.getAttribute("signed-stream-name")
143
+ return {
144
+ channel: channel,
145
+ signed_stream_name: signed_stream_name,
146
+ session_id: this.sessionId,
147
+ peer_id: this.peerId,
148
+ }
149
+ }
150
+ }
151
+
152
+ customElements.define("p2p-frame", P2pFrameElement)