mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-15 15:37:36 +00:00
Merge pull request #13463 from overleaf/jpa-bootstrap-ws
[misc] automatically call joinProject as part of connecting to real-time GitOrigin-RevId: 2466168e9cebb62dec07481273050efcd0478114
This commit is contained in:
parent
bffe76ff26
commit
55c5330108
4 changed files with 719 additions and 64 deletions
|
@ -119,6 +119,8 @@ module.exports = Router = {
|
|||
)
|
||||
|
||||
session.on('connection', function (error, client, session) {
|
||||
const joinProjectAutomatically = !!client.handshake.query.projectId
|
||||
|
||||
// init client context, we may access it in Router._handleError before
|
||||
// setting any values
|
||||
client.ol_context = {}
|
||||
|
@ -172,13 +174,18 @@ module.exports = Router = {
|
|||
|
||||
// send positive confirmation that the client has a valid connection
|
||||
client.publicId = 'P.' + base64id.generateId()
|
||||
client.emit('connectionAccepted', null, client.publicId)
|
||||
if (!joinProjectAutomatically) {
|
||||
client.emit('connectionAccepted', null, client.publicId)
|
||||
}
|
||||
|
||||
client.remoteIp = websocketAddressManager.getRemoteIp(client.handshake)
|
||||
const headers = client.handshake && client.handshake.headers
|
||||
client.userAgent = headers && headers['user-agent']
|
||||
|
||||
metrics.inc('socket-io.connection', 1, { status: client.transport })
|
||||
metrics.inc('socket-io.connection', 1, {
|
||||
status: client.transport,
|
||||
method: joinProjectAutomatically ? 'auto-join-project' : undefined,
|
||||
})
|
||||
metrics.gauge('socket-io.clients', io.sockets.clients().length)
|
||||
|
||||
logger.debug({ session, clientId: client.id }, 'client connected')
|
||||
|
@ -205,7 +212,7 @@ module.exports = Router = {
|
|||
})
|
||||
}
|
||||
|
||||
client.on('joinProject', function (data, callback) {
|
||||
const joinProject = function (data, callback) {
|
||||
data = data || {}
|
||||
if (typeof callback !== 'function') {
|
||||
return Router._handleInvalidArguments(
|
||||
|
@ -260,7 +267,8 @@ module.exports = Router = {
|
|||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
client.on('joinProject', joinProject)
|
||||
|
||||
client.on('disconnect', function () {
|
||||
metrics.inc('socket-io.disconnect', 1, { status: client.transport })
|
||||
|
@ -455,6 +463,27 @@ module.exports = Router = {
|
|||
}
|
||||
)
|
||||
})
|
||||
|
||||
if (joinProjectAutomatically) {
|
||||
const { projectId } = client.handshake.query
|
||||
const anonymousAccessToken = session?.anonTokenAccess?.[projectId]
|
||||
joinProject(
|
||||
{ project_id: projectId, anonymousAccessToken },
|
||||
(err, project, permissionsLevel, protocolVersion) => {
|
||||
if (err) {
|
||||
client.emit('connectionRejected', err)
|
||||
client.disconnect()
|
||||
return
|
||||
}
|
||||
client.emit('joinProjectResponse', {
|
||||
publicId: client.publicId,
|
||||
project,
|
||||
permissionsLevel,
|
||||
protocolVersion,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
|
|
@ -519,7 +519,7 @@ describe('joinProject', function () {
|
|||
})
|
||||
})
|
||||
|
||||
return describe('when over rate limit', function () {
|
||||
describe('when over rate limit', function () {
|
||||
before(function (done) {
|
||||
return async.series(
|
||||
[
|
||||
|
@ -548,4 +548,560 @@ describe('joinProject', function () {
|
|||
return this.error.code.should.equal('TooManyRequests')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when automatically joining the project', function () {
|
||||
describe('when authorized', function () {
|
||||
let connectionAcceptedReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
privilegeLevel: 'owner',
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.project_id = projectId
|
||||
this.user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionAccepted', () => {
|
||||
connectionAcceptedReceived = true
|
||||
})
|
||||
this.client.on('connectionRejected', cb)
|
||||
this.client.on(
|
||||
'joinProjectResponse',
|
||||
({ project, permissionsLevel, protocolVersion }) => {
|
||||
this.project = project
|
||||
this.permissionsLevel = permissionsLevel
|
||||
this.protocolVersion = protocolVersion
|
||||
cb()
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit connectionAccepted', function () {
|
||||
expect(connectionAcceptedReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should get the project from web', function () {
|
||||
MockWebServer.joinProject
|
||||
.calledWith(this.project_id, this.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return the project', function () {
|
||||
this.project.should.deep.equal({
|
||||
name: 'Test Project',
|
||||
owner: { _id: this.user_id },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the privilege level', function () {
|
||||
this.permissionsLevel.should.equal('owner')
|
||||
})
|
||||
|
||||
it('should return the protocolVersion', function () {
|
||||
this.protocolVersion.should.equal(2)
|
||||
})
|
||||
|
||||
it('should have joined the project room', function (done) {
|
||||
RealTimeClient.getConnectedClient(
|
||||
this.client.socket.sessionid,
|
||||
(error, client) => {
|
||||
if (error) return done(error)
|
||||
expect(Array.from(client.rooms).includes(this.project_id)).to.equal(
|
||||
true
|
||||
)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should have marked the user as connected', function (done) {
|
||||
this.client.emit('clientTracking.getConnectedUsers', (error, users) => {
|
||||
if (error) return done(error)
|
||||
let connected = false
|
||||
for (const user of Array.from(users)) {
|
||||
if (
|
||||
user.client_id === this.client.publicId &&
|
||||
user.user_id === this.user_id
|
||||
) {
|
||||
connected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
expect(connected).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when authorized with token', function () {
|
||||
let connectionAcceptedReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
privilegeLevel: 'owner',
|
||||
publicAccess: 'readOnly',
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(
|
||||
e,
|
||||
{
|
||||
user_id: ownerId,
|
||||
project_id: projectId,
|
||||
anonymousAccessToken,
|
||||
}
|
||||
) => {
|
||||
this.ownerId = ownerId
|
||||
this.project_id = projectId
|
||||
this.anonymousAccessToken = anonymousAccessToken
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
RealTimeClient.setSession(
|
||||
{
|
||||
anonTokenAccess: {
|
||||
[this.project_id]: this.anonymousAccessToken,
|
||||
},
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionAccepted', () => {
|
||||
connectionAcceptedReceived = true
|
||||
})
|
||||
this.client.on('connectionRejected', cb)
|
||||
this.client.on(
|
||||
'joinProjectResponse',
|
||||
({ project, permissionsLevel, protocolVersion }) => {
|
||||
this.project = project
|
||||
this.permissionsLevel = permissionsLevel
|
||||
this.protocolVersion = protocolVersion
|
||||
cb()
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit connectionAccepted', function () {
|
||||
expect(connectionAcceptedReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should get the project from web', function () {
|
||||
MockWebServer.joinProject
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
'anonymous-user',
|
||||
this.anonymousAccessToken
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return the project', function () {
|
||||
this.project.should.deep.equal({
|
||||
name: 'Test Project',
|
||||
owner: { _id: this.ownerId },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the privilege level', function () {
|
||||
this.permissionsLevel.should.equal('readOnly')
|
||||
})
|
||||
|
||||
it('should return the protocolVersion', function () {
|
||||
this.protocolVersion.should.equal(2)
|
||||
})
|
||||
|
||||
it('should have joined the project room', function (done) {
|
||||
RealTimeClient.getConnectedClient(
|
||||
this.client.socket.sessionid,
|
||||
(error, client) => {
|
||||
if (error) return done(error)
|
||||
expect(Array.from(client.rooms).includes(this.project_id)).to.equal(
|
||||
true
|
||||
)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should have marked the user as connected', function (done) {
|
||||
this.client.emit('clientTracking.getConnectedUsers', (error, users) => {
|
||||
if (error) return done(error)
|
||||
let connected = false
|
||||
for (const user of Array.from(users)) {
|
||||
if (user.client_id === this.client.publicId) {
|
||||
connected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
expect(connected).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when not authorized', function () {
|
||||
let joinProjectResponseReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
privilegeLevel: null,
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.project_id = projectId
|
||||
this.user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionRejected', err => {
|
||||
this.error = err
|
||||
cb()
|
||||
})
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
joinProjectResponseReceived = true
|
||||
cb()
|
||||
})
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit joinProjectResponse', function () {
|
||||
expect(joinProjectResponseReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should have disconnected the client', function () {
|
||||
expect(this.client.socket.connected).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return an error', function () {
|
||||
this.error.message.should.equal('not authorized')
|
||||
})
|
||||
|
||||
it('should not have joined the project room', function (done) {
|
||||
RealTimeClient.getConnectedClient(
|
||||
this.client.socket.sessionid,
|
||||
error => {
|
||||
expect(error.message).to.equal('not found')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when not authorized and web replies with a 403', function () {
|
||||
let joinProjectResponseReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
project_id: '403403403403403403403403', // forbidden
|
||||
privilegeLevel: 'owner',
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.project_id = projectId
|
||||
this.user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionRejected', err => {
|
||||
this.error = err
|
||||
cb()
|
||||
})
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
joinProjectResponseReceived = true
|
||||
cb()
|
||||
})
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit joinProjectResponse', function () {
|
||||
expect(joinProjectResponseReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should have disconnected the client', function () {
|
||||
expect(this.client.socket.connected).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return an error', function () {
|
||||
this.error.message.should.equal('not authorized')
|
||||
})
|
||||
|
||||
it('should not have joined the project room', function (done) {
|
||||
RealTimeClient.getConnectedClient(
|
||||
this.client.socket.sessionid,
|
||||
error => {
|
||||
expect(error.message).to.equal('not found')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when deleted and web replies with a 404', function () {
|
||||
let joinProjectResponseReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
project_id: '404404404404404404404404', // not-found
|
||||
privilegeLevel: 'owner',
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.project_id = projectId
|
||||
this.user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionRejected', err => {
|
||||
this.error = err
|
||||
cb()
|
||||
})
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
joinProjectResponseReceived = true
|
||||
cb()
|
||||
})
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit joinProjectResponse', function () {
|
||||
expect(joinProjectResponseReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should have disconnected the client', function () {
|
||||
expect(this.client.socket.connected).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return an error', function () {
|
||||
this.error.code.should.equal('ProjectNotFound')
|
||||
})
|
||||
|
||||
it('should not have joined the project room', function (done) {
|
||||
RealTimeClient.getConnectedClient(
|
||||
this.client.socket.sessionid,
|
||||
error => {
|
||||
expect(error.message).to.equal('not found')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when invalid', function () {
|
||||
let joinProjectResponseReceived = false
|
||||
before(function (done) {
|
||||
MockWebServer.joinProject.resetHistory()
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect('projectId=invalid-id')
|
||||
this.client.on('connectionRejected', err => {
|
||||
this.error = err
|
||||
cb()
|
||||
})
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
joinProjectResponseReceived = true
|
||||
cb()
|
||||
})
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit joinProjectResponse', function () {
|
||||
expect(joinProjectResponseReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should have disconnected the client', function () {
|
||||
expect(this.client.socket.connected).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return an invalid id error', function () {
|
||||
this.error.message.should.equal('invalid id')
|
||||
})
|
||||
|
||||
it('should not call to web', function () {
|
||||
MockWebServer.joinProject.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when joining more than one project', function () {
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
privilegeLevel: 'owner',
|
||||
project: {
|
||||
name: 'Other Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.other_project_id = projectId
|
||||
this.other_user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
FixturesManager.setUpProject(
|
||||
{
|
||||
user_id: this.other_user_id,
|
||||
privilegeLevel: 'owner',
|
||||
project: {
|
||||
name: 'Test Project',
|
||||
},
|
||||
},
|
||||
(e, { project_id: projectId, user_id: userId }) => {
|
||||
this.project_id = projectId
|
||||
this.user_id = userId
|
||||
cb(e)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
`projectId=${this.project_id}`
|
||||
)
|
||||
this.client.on('connectionRejected', cb)
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
cb()
|
||||
})
|
||||
},
|
||||
|
||||
cb => {
|
||||
this.client.emit(
|
||||
'joinProject',
|
||||
{ project_id: this.other_project_id },
|
||||
error => {
|
||||
this.error = error
|
||||
cb()
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should return an error', function () {
|
||||
this.error.message.should.equal('cannot join multiple projects')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when over rate limit', function () {
|
||||
let joinProjectResponseReceived = false
|
||||
before(function (done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
this.client = RealTimeClient.connect(
|
||||
'projectId=429429429429429429429429'
|
||||
)
|
||||
this.client.on('connectionRejected', err => {
|
||||
this.error = err
|
||||
cb()
|
||||
})
|
||||
this.client.on('joinProjectResponse', () => {
|
||||
joinProjectResponseReceived = true
|
||||
cb()
|
||||
})
|
||||
},
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should not emit joinProjectResponse', function () {
|
||||
expect(joinProjectResponseReceived).to.equal(false)
|
||||
})
|
||||
|
||||
it('should have disconnected the client', function () {
|
||||
expect(this.client.socket.connected).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return a TooManyRequests error code', function () {
|
||||
this.error.message.should.equal('rate-limit hit when joining project')
|
||||
this.error.code.should.equal('TooManyRequests')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -62,14 +62,19 @@ module.exports = Client = {
|
|||
return callback()
|
||||
},
|
||||
|
||||
connect(cookie) {
|
||||
connect(query) {
|
||||
const client = io.connect('http://localhost:3026', {
|
||||
'force new connection': true,
|
||||
query,
|
||||
})
|
||||
client.on(
|
||||
'connectionAccepted',
|
||||
(_, publicId) => (client.publicId = publicId)
|
||||
)
|
||||
client.on(
|
||||
'joinProjectResponse',
|
||||
({ publicId }) => (client.publicId = publicId)
|
||||
)
|
||||
return client
|
||||
},
|
||||
|
||||
|
@ -95,7 +100,13 @@ module.exports = Client = {
|
|||
url: `http://localhost:3026/clients/${clientId}`,
|
||||
json: true,
|
||||
},
|
||||
(error, response, data) => callback(error, data)
|
||||
(error, response, data) => {
|
||||
if (response?.statusCode === 404) {
|
||||
callback(new Error('not found'))
|
||||
} else {
|
||||
callback(error, data)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
/* global io */
|
||||
|
||||
import SocketIoShim from './SocketIoShim'
|
||||
import getMeta from '../../utils/meta'
|
||||
|
||||
let ConnectionManager
|
||||
const ONEHOUR = 1000 * 60 * 60
|
||||
|
@ -130,11 +131,15 @@ export default ConnectionManager = (function () {
|
|||
pathname: this.wsUrl || '/socket.io',
|
||||
}
|
||||
}
|
||||
const query = new URLSearchParams({
|
||||
projectId: getMeta('ol-project_id'),
|
||||
}).toString()
|
||||
this.ide.socket = SocketIoShim.connect(parsedURL.origin, {
|
||||
resource: parsedURL.pathname.slice(1),
|
||||
reconnect: false,
|
||||
'connect timeout': 30 * 1000,
|
||||
'force new connection': true,
|
||||
query,
|
||||
})
|
||||
|
||||
// handle network-level websocket errors (e.g. failed dns lookups)
|
||||
|
@ -176,9 +181,10 @@ export default ConnectionManager = (function () {
|
|||
})
|
||||
|
||||
// The next event we should get is an authentication response
|
||||
// from the server, either "connectionAccepted" or
|
||||
// from the server, either "connectionAccepted" or "bootstrap" or
|
||||
// "connectionRejected".
|
||||
|
||||
// Handle real-time without bootstrap capability.
|
||||
this.ide.socket.on('connectionAccepted', (_, publicId) => {
|
||||
this.ide.socket.publicId = publicId || this.ide.socket.socket.sessionid
|
||||
// state should be 'authenticating'...
|
||||
|
@ -201,15 +207,54 @@ export default ConnectionManager = (function () {
|
|||
}, 100)
|
||||
})
|
||||
|
||||
this.ide.socket.on(
|
||||
'joinProjectResponse',
|
||||
({ publicId, project, permissionsLevel, protocolVersion }) => {
|
||||
this.ide.socket.publicId = publicId
|
||||
sl_console.log('[socket.io bootstrap] ready for joinDoc')
|
||||
this.connected = true
|
||||
this.gracefullyReconnecting = false
|
||||
this.ide.pushEvent('connected')
|
||||
this.ide.pushEvent('joinProjectResponse')
|
||||
this.updateConnectionManagerState('joining')
|
||||
|
||||
this.$scope.$apply(() => {
|
||||
if (this.$scope.state.loading) {
|
||||
this.$scope.state.load_progress = 70
|
||||
}
|
||||
})
|
||||
|
||||
const connectionJobId = this.$scope.connection.jobId
|
||||
this.handleJoinProjectResponse({
|
||||
connectionJobId,
|
||||
project,
|
||||
permissionsLevel,
|
||||
protocolVersion,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
this.ide.socket.on('connectionRejected', err => {
|
||||
// state should be 'authenticating'...
|
||||
sl_console.log(
|
||||
'[socket.io connectionRejected] session not valid or other connection error'
|
||||
)
|
||||
// real time sends a 'retry' message if the process was shutting down
|
||||
if (err && err.message === 'retry') {
|
||||
// real-time sends a 'retry' message if the process was shutting down
|
||||
// real-time sends TooManyRequests if joinProject was rate-limited.
|
||||
if (err?.message === 'retry' || err?.code === 'TooManyRequests') {
|
||||
return this.tryReconnectWithRateLimit()
|
||||
}
|
||||
if (err?.code === 'ProjectNotFound') {
|
||||
// A stale browser tab tried to join a deleted project.
|
||||
// Reloading the page will render a 404.
|
||||
this.ide
|
||||
.showGenericMessageModal(
|
||||
'Project has been deleted',
|
||||
'This project has been deleted by the owner.'
|
||||
)
|
||||
.result.then(() => location.reload(true))
|
||||
return
|
||||
}
|
||||
// we have failed authentication, usually due to an invalid session cookie
|
||||
return this.reportConnectionError(err)
|
||||
})
|
||||
|
@ -396,65 +441,79 @@ Something went wrong connecting to your project. Please refresh if this continue
|
|||
'joinProject',
|
||||
data,
|
||||
(err, project, permissionsLevel, protocolVersion) => {
|
||||
if (err != null || project == null) {
|
||||
err = err || {}
|
||||
if (err.code === 'ProjectNotFound') {
|
||||
// A stale browser tab tried to join a deleted project.
|
||||
// Reloading the page will render a 404.
|
||||
this.ide
|
||||
.showGenericMessageModal(
|
||||
'Project has been deleted',
|
||||
'This project has been deleted by the owner.'
|
||||
)
|
||||
.result.then(() => location.reload(true))
|
||||
return
|
||||
}
|
||||
if (err.code === 'TooManyRequests') {
|
||||
sl_console.log(
|
||||
`[joinProject ${connectionId}] retrying: ${err.message}`
|
||||
)
|
||||
setTimeout(
|
||||
() => this.joinProject(connectionId),
|
||||
this.joinProjectRetryInterval
|
||||
)
|
||||
if (
|
||||
this.joinProjectRetryInterval <
|
||||
this.JOIN_PROJECT_MAX_RETRY_INTERVAL
|
||||
) {
|
||||
this.joinProjectRetryInterval +=
|
||||
this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return this.reportConnectionError(err)
|
||||
}
|
||||
}
|
||||
|
||||
this.joinProjectRetryInterval = this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
|
||||
if (
|
||||
this.$scope.protocolVersion != null &&
|
||||
this.$scope.protocolVersion !== protocolVersion
|
||||
) {
|
||||
location.reload(true)
|
||||
}
|
||||
|
||||
this.$scope.$apply(() => {
|
||||
this.updateConnectionManagerState('ready')
|
||||
this.$scope.protocolVersion = protocolVersion
|
||||
const defaultProjectAttributes = { rootDoc_id: null }
|
||||
this.$scope.project = { ...defaultProjectAttributes, ...project }
|
||||
this.$scope.permissionsLevel = permissionsLevel
|
||||
this.ide.loadingManager.socketLoaded()
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('project:joined', { detail: this.$scope.project })
|
||||
)
|
||||
this.$scope.$broadcast('project:joined')
|
||||
this.handleJoinProjectResponse({
|
||||
connectionId,
|
||||
err,
|
||||
project,
|
||||
permissionsLevel,
|
||||
protocolVersion,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleJoinProjectResponse({
|
||||
connectionId,
|
||||
err,
|
||||
project,
|
||||
permissionsLevel,
|
||||
protocolVersion,
|
||||
}) {
|
||||
if (err != null || project == null) {
|
||||
err = err || {}
|
||||
if (err.code === 'ProjectNotFound') {
|
||||
// A stale browser tab tried to join a deleted project.
|
||||
// Reloading the page will render a 404.
|
||||
this.ide
|
||||
.showGenericMessageModal(
|
||||
'Project has been deleted',
|
||||
'This project has been deleted by the owner.'
|
||||
)
|
||||
.result.then(() => location.reload(true))
|
||||
return
|
||||
}
|
||||
if (err.code === 'TooManyRequests') {
|
||||
sl_console.log(
|
||||
`[joinProject ${connectionId}] retrying: ${err.message}`
|
||||
)
|
||||
setTimeout(
|
||||
() => this.joinProject(connectionId),
|
||||
this.joinProjectRetryInterval
|
||||
)
|
||||
if (
|
||||
this.joinProjectRetryInterval < this.JOIN_PROJECT_MAX_RETRY_INTERVAL
|
||||
) {
|
||||
this.joinProjectRetryInterval += this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return this.reportConnectionError(err)
|
||||
}
|
||||
}
|
||||
|
||||
this.joinProjectRetryInterval = this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
|
||||
if (
|
||||
this.$scope.protocolVersion != null &&
|
||||
this.$scope.protocolVersion !== protocolVersion
|
||||
) {
|
||||
location.reload(true)
|
||||
}
|
||||
|
||||
this.$scope.$apply(() => {
|
||||
this.updateConnectionManagerState('ready')
|
||||
this.$scope.protocolVersion = protocolVersion
|
||||
const defaultProjectAttributes = { rootDoc_id: null }
|
||||
this.$scope.project = { ...defaultProjectAttributes, ...project }
|
||||
this.$scope.permissionsLevel = permissionsLevel
|
||||
this.ide.loadingManager.socketLoaded()
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('project:joined', { detail: this.$scope.project })
|
||||
)
|
||||
this.$scope.$broadcast('project:joined')
|
||||
})
|
||||
}
|
||||
|
||||
reconnectImmediately() {
|
||||
this.disconnect()
|
||||
return this.tryReconnect()
|
||||
|
|
Loading…
Add table
Reference in a new issue