p2p_streams_channel 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)