From be5fb0b58486ae12239e8bf91f37e1fd7fe81ce1 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 1 Dec 2023 13:55:59 -0800 Subject: [PATCH 1/5] auto-cork, sse not working Signed-off-by: Matt Krick --- packages/server/graphql/httpGraphQLHandler.ts | 12 +- .../server/graphql/intranetGraphQLHandler.ts | 4 +- packages/server/jiraImagesHandler.ts | 14 +- packages/server/pipeStreamOverResponse.ts | 4 +- packages/server/safetyPatchRes.ts | 42 +++-- .../server/socketHandlers/handleUpgrade.ts | 7 +- packages/server/sse/whm.js | 163 ------------------ packages/server/utils/SAMLHandler.ts | 16 +- packages/server/utils/serveStatic.ts | 14 +- 9 files changed, 57 insertions(+), 219 deletions(-) delete mode 100644 packages/server/sse/whm.js diff --git a/packages/server/graphql/httpGraphQLHandler.ts b/packages/server/graphql/httpGraphQLHandler.ts index 36b4f81d6f2..b6c559b181a 100644 --- a/packages/server/graphql/httpGraphQLHandler.ts +++ b/packages/server/graphql/httpGraphQLHandler.ts @@ -56,13 +56,11 @@ const httpGraphQLBodyHandler = async ( } } const response = await handleGraphQLTrebuchetRequest(body, connectionContext) - res.cork(() => { - if (response) { - res.writeHeader('content-type', 'application/json').end(JSON.stringify(response)) - } else { - res.writeStatus('200').end() - } - }) + if (response) { + res.writeHeader('content-type', 'application/json').end(JSON.stringify(response)) + } else { + res.writeStatus('200').end() + } } const contentTypeBodyParserMap = { diff --git a/packages/server/graphql/intranetGraphQLHandler.ts b/packages/server/graphql/intranetGraphQLHandler.ts index 35d769be5e3..f877abc2abe 100644 --- a/packages/server/graphql/intranetGraphQLHandler.ts +++ b/packages/server/graphql/intranetGraphQLHandler.ts @@ -38,9 +38,7 @@ const intranetHttpGraphQLHandler = uWSAsyncHandler(async (res: HttpResponse, req isPrivate, isAdHoc: true }) - res.cork(() => { - res.writeHeader('content-type', 'application/json').end(JSON.stringify(result)) - }) + res.writeHeader('content-type', 'application/json').end(JSON.stringify(result)) } catch (e) { res.writeStatus('502').end() } diff --git a/packages/server/jiraImagesHandler.ts b/packages/server/jiraImagesHandler.ts index 9197d7d6a02..64d7c787b83 100644 --- a/packages/server/jiraImagesHandler.ts +++ b/packages/server/jiraImagesHandler.ts @@ -35,9 +35,7 @@ const servePlaceholderImage = async (res: HttpResponse) => { path.join(__dirname, jiraPlaceholder.slice(__webpack_public_path__.length)) ) } - res.cork(() => { - res.writeStatus('200').writeHeader('Content-Type', 'image/png').end(jiraPlaceholderBuffer) - }) + res.writeStatus('200').writeHeader('Content-Type', 'image/png').end(jiraPlaceholderBuffer) } const jiraImagesHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpRequest) => { @@ -53,12 +51,10 @@ const jiraImagesHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpReq return } - res.cork(() => { - res - .writeStatus('200') - .writeHeader('Content-Type', cachedImage.contentType) - .end(cachedImage.imageBuffer) - }) + res + .writeStatus('200') + .writeHeader('Content-Type', cachedImage.contentType) + .end(cachedImage.imageBuffer) }) export default jiraImagesHandler diff --git a/packages/server/pipeStreamOverResponse.ts b/packages/server/pipeStreamOverResponse.ts index ba7d18ba1d4..313d28e35b0 100644 --- a/packages/server/pipeStreamOverResponse.ts +++ b/packages/server/pipeStreamOverResponse.ts @@ -41,7 +41,9 @@ const pipeStreamOverResponse = ( .on('error', () => { if (!res.aborted) { - res.writeStatus('500').end() + res.cork(() => { + res.writeStatus('500').end() + }) } readStream.destroy() }) diff --git a/packages/server/safetyPatchRes.ts b/packages/server/safetyPatchRes.ts index d21ed31238e..d48e736b5cf 100644 --- a/packages/server/safetyPatchRes.ts +++ b/packages/server/safetyPatchRes.ts @@ -1,5 +1,7 @@ import {HttpResponse, RecognizedString} from 'uWebSockets.js' +type Header = [key: RecognizedString, value: RecognizedString] + const safetyPatchRes = (res: HttpResponse) => { if (res._end) { throw new Error('already patched') @@ -17,6 +19,20 @@ const safetyPatchRes = (res: HttpResponse) => { return res } + // Cache writes until `.end()` gets called. Then flush + res.status = '' + res.headers = [] as Header[] + + const flush = (thunk: () => T) => { + return res._cork(() => { + if (res.status) res._writeStatus(res.status) + res.headers.forEach((header: Header) => { + res._writeHeader(...header) + }) + return thunk() + }) + } + res._end = res.end res.end = (body?: RecognizedString) => { if (res.done) { @@ -24,7 +40,7 @@ const safetyPatchRes = (res: HttpResponse) => { } if (res.done || res.aborted) return res res.done = true - return res._end(body) + return flush(() => res._end(body)) } res._close = res.close @@ -38,12 +54,8 @@ const safetyPatchRes = (res: HttpResponse) => { } res._cork = res.cork - res.cork = (cb: () => void) => { - if (res.done) { - console.warn(`uWS: Called cork after done`) - } - if (res.done || res.aborted) return res - return res._cork(cb) + res.cork = () => { + throw new Error('safetyPatchRes applies the cork for you, do not call directly') } res._tryEnd = res.tryEnd @@ -52,7 +64,7 @@ const safetyPatchRes = (res: HttpResponse) => { console.warn(`uWS: Called tryEnd after done`) } if (res.done || res.aborted) return [true, true] - return res._tryEnd(fullBodyOrChunk, totalSize) + return flush(() => res._tryEnd(fullBodyOrChunk, totalSize)) } res._write = res.write @@ -69,8 +81,8 @@ const safetyPatchRes = (res: HttpResponse) => { if (res.done) { console.warn(`uWS: Called writeHeader after done`) } - if (res.done || res.aborted) return res - return res._writeHeader(key, value) + res.headers.push([key, value]) + return res } res._writeStatus = res.writeStatus @@ -78,8 +90,8 @@ const safetyPatchRes = (res: HttpResponse) => { if (res.done) { console.error(`uWS: Called writeStatus after done ${status}`) } - if (res.done || res.aborted) return res - return res._writeStatus(status) + res.status = status + return res } res._upgrade = res.upgrade @@ -88,13 +100,15 @@ const safetyPatchRes = (res: HttpResponse) => { console.error(`uWS: Called upgrade after done`) } if (res.done || res.aborted) return - return res._upgrade(...args) + return res._cork(() => { + res._upgrade(...args) + }) } res._getRemoteAddressAsText = res.getRemoteAddressAsText res.getRemoteAddressAsText = () => { if (res.done) { - console.error(`uWS: Called upgrade after done`) + console.error(`uWS: Called getRemoteAddressAsText after done`) } if (res.done || res.aborted) return Buffer.from('') return res._getRemoteAddressAsText() diff --git a/packages/server/socketHandlers/handleUpgrade.ts b/packages/server/socketHandlers/handleUpgrade.ts index 7eeb9668685..07874d2783d 100644 --- a/packages/server/socketHandlers/handleUpgrade.ts +++ b/packages/server/socketHandlers/handleUpgrade.ts @@ -1,5 +1,6 @@ import {WebSocketBehavior} from 'uWebSockets.js' import {TrebuchetCloseReason} from '../../client/types/constEnums' +import safetyPatchRes from '../safetyPatchRes' import {isAuthenticated} from '../utils/authorization' import checkBlacklistJWT from '../utils/checkBlacklistJWT' import getQueryToken from '../utils/getQueryToken' @@ -7,6 +8,7 @@ import sendToSentry from '../utils/sendToSentry' import uwsGetIP from '../utils/uwsGetIP' const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, context) => { + safetyPatchRes(res) const protocol = req.getHeader('sec-websocket-protocol') if (protocol !== 'trebuchet-ws') { sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) @@ -19,9 +21,6 @@ const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, conte res.writeStatus('401').end() return } - res.onAborted(() => { - res.aborted = true - }) const key = req.getHeader('sec-websocket-key') const extensions = req.getHeader('sec-websocket-extensions') @@ -29,12 +28,10 @@ const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, conte const {sub: userId, iat} = authToken // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) - if (res.aborted) return if (isBlacklistedJWT) { res.writeStatus('401').end(TrebuchetCloseReason.EXPIRED_SESSION) return } - res.upgrade({ip, authToken}, key, protocol, extensions, context) } diff --git a/packages/server/sse/whm.js b/packages/server/sse/whm.js deleted file mode 100644 index 7994de24ee5..00000000000 --- a/packages/server/sse/whm.js +++ /dev/null @@ -1,163 +0,0 @@ -module.exports = webpackHotMiddleware - -function webpackHotMiddleware(compiler, opts) { - opts = opts || {} - opts.log = - typeof opts.log == 'undefined' ? console.log.bind(console) : opts.log - opts.path = opts.path || '/__webpack_hmr' - opts.heartbeat = opts.heartbeat || 10 * 1000 - - var eventStream = createEventStream(opts.heartbeat) - var latestStats = null - var closed = false - - if (compiler.hooks) { - compiler.hooks.invalid.tap('webpack-hot-middleware', onInvalid) - compiler.hooks.done.tap('webpack-hot-middleware', onDone) - } else { - compiler.plugin('invalid', onInvalid) - compiler.plugin('done', onDone) - } - function onInvalid() { - if (closed) return - latestStats = null - if (opts.log) opts.log('webpack building...') - eventStream.publish({action: 'building'}) - } - function onDone(statsResult) { - if (closed) return - // Keep hold of latest stats so they can be propagated to new clients - latestStats = statsResult - publishStats('built', latestStats, eventStream, opts.log) - } - var middleware = function (req, res, next) { - if (closed) return next() - if (!req.url.includes(opts.path)) return next() - // if (!pathMatch(req.url, opts.path)) return next() - eventStream.handler(req, res) - if (latestStats) { - // Explicitly not passing in `log` fn as we don't want to log again on - // the server - publishStats('sync', latestStats, eventStream) - } - } - middleware.publish = function (payload) { - if (closed) return - eventStream.publish(payload) - } - middleware.close = function () { - if (closed) return - // Can't remove compiler plugins, so we just set a flag and noop if closed - // https://github.com/webpack/tapable/issues/32#issuecomment-350644466 - closed = true - eventStream.close() - eventStream = null - } - return middleware -} - -function createEventStream(heartbeat) { - const clients = [] - function everyClient(fn) { - clients.forEach((client) => { - if (client.done) { - clients.splice(clients.indexOf(client), 1) - } else { - fn(client) - } - }) - } - var interval = setInterval(function heartbeatTick() { - everyClient(function (client) { - client.tryEnd('data: \uD83D\uDC93\n\n', 1e8) - }) - }, heartbeat).unref() - return { - close: function () { - clearInterval(interval) - everyClient(function (client) { - client.end() - }) - clients.length = 0 - }, - handler: function (_req, res) { - if (res.done) return - res.writeHeader('Access-Control-Allow-Origin', '*') - res.writeHeader('Content-Type', 'text/event-stream;charset=utf-8') - res.writeHeader('Connection', 'keep-alive') - res.writeHeader('Cache-Control', 'no-cache, no-transform') - res.writeHeader('X-Accel-Buffering', 'no') - res.tryEnd(`retry: 1000\n`, 1e8) - res.tryEnd('\n', 1e8) - clients.push(res) - res.onAborted(() => { - clients.splice(clients.indexOf(res), 1) - }) - }, - publish: function (payload) { - everyClient(function (client) { - client.tryEnd('data: ' + JSON.stringify(payload) + '\n\n', 1e8) - }) - }, - } -} - -function publishStats(action, statsResult, eventStream, log) { - var stats = statsResult.toJson({ - all: false, - cached: true, - children: true, - modules: true, - timings: true, - hash: true, - }) - // For multi-compiler, stats will be an object with a 'children' array of stats - var bundles = extractBundles(stats) - bundles.forEach(function (stats) { - var name = stats.name || '' - - // Fallback to compilation name in case of 1 bundle (if it exists) - if (bundles.length === 1 && !name && statsResult.compilation) { - name = statsResult.compilation.name || '' - } - - if (log) { - log( - 'webpack built ' + - (name ? name + ' ' : '') + - stats.hash + - ' in ' + - stats.time + - 'ms' - ) - } - eventStream.publish({ - name: name, - action: action, - time: stats.time, - hash: stats.hash, - warnings: stats.warnings || [], - errors: stats.errors || [], - modules: buildModuleMap(stats.modules), - }) - }) -} - -function extractBundles(stats) { - // Stats has modules, single bundle - if (stats.modules) return [stats] - - // Stats has children, multiple bundles - if (stats.children && stats.children.length) return stats.children - - // Not sure, assume single - return [stats] -} - -function buildModuleMap(modules) { - var map = {} - modules.forEach(function (module) { - map[module.id] = module.name - }) - return map -} diff --git a/packages/server/utils/SAMLHandler.ts b/packages/server/utils/SAMLHandler.ts index c4cfc4a726c..0b3b2b67254 100644 --- a/packages/server/utils/SAMLHandler.ts +++ b/packages/server/utils/SAMLHandler.ts @@ -47,15 +47,13 @@ const SAMLHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpRequest) redirectOnError(res, message) return } - res.cork(() => { - res - .writeStatus('302') - .writeHeader( - 'location', - `/saml-redirect?userId=${userId}&token=${authToken}&isNewUser=${isNewUser}&isPatient0=${user.isPatient0}` - ) - .end() - }) + res + .writeStatus('302') + .writeHeader( + 'location', + `/saml-redirect?userId=${userId}&token=${authToken}&isNewUser=${isNewUser}&isPatient0=${user.isPatient0}` + ) + .end() }) export default SAMLHandler diff --git a/packages/server/utils/serveStatic.ts b/packages/server/utils/serveStatic.ts index 41bf4a090d8..2448123077b 100644 --- a/packages/server/utils/serveStatic.ts +++ b/packages/server/utils/serveStatic.ts @@ -29,14 +29,12 @@ const serveStatic = (res: HttpResponse, fileName: string, sendCompressed?: boole if (!meta) return false const {size, pathname, brotliFile, file, type} = meta if (file) { - res.cork(() => { - res.writeHeader('content-type', type) - if (__PRODUCTION__ && sendCompressed && brotliFile) { - res.writeHeader('content-encoding', 'br').end(brotliFile) - } else { - res.end(file) - } - }) + res.writeHeader('content-type', type) + if (__PRODUCTION__ && sendCompressed && brotliFile) { + res.writeHeader('content-encoding', 'br').end(brotliFile) + } else { + res.end(file) + } return true } res.writeHeader('content-type', type) From d486bc3ed1942b84183265a3e01204a113cb2fbf Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 1 Dec 2023 13:55:59 -0800 Subject: [PATCH 2/5] fix: auto-cork, sse not working Signed-off-by: Matt Krick --- packages/server/graphql/httpGraphQLHandler.ts | 12 +- .../server/graphql/intranetGraphQLHandler.ts | 4 +- packages/server/jiraImagesHandler.ts | 14 +- packages/server/pipeStreamOverResponse.ts | 4 +- packages/server/safetyPatchRes.ts | 42 +++-- .../server/socketHandlers/handleUpgrade.ts | 7 +- packages/server/sse/whm.js | 163 ------------------ packages/server/utils/SAMLHandler.ts | 16 +- packages/server/utils/serveStatic.ts | 14 +- 9 files changed, 57 insertions(+), 219 deletions(-) delete mode 100644 packages/server/sse/whm.js diff --git a/packages/server/graphql/httpGraphQLHandler.ts b/packages/server/graphql/httpGraphQLHandler.ts index 36b4f81d6f2..b6c559b181a 100644 --- a/packages/server/graphql/httpGraphQLHandler.ts +++ b/packages/server/graphql/httpGraphQLHandler.ts @@ -56,13 +56,11 @@ const httpGraphQLBodyHandler = async ( } } const response = await handleGraphQLTrebuchetRequest(body, connectionContext) - res.cork(() => { - if (response) { - res.writeHeader('content-type', 'application/json').end(JSON.stringify(response)) - } else { - res.writeStatus('200').end() - } - }) + if (response) { + res.writeHeader('content-type', 'application/json').end(JSON.stringify(response)) + } else { + res.writeStatus('200').end() + } } const contentTypeBodyParserMap = { diff --git a/packages/server/graphql/intranetGraphQLHandler.ts b/packages/server/graphql/intranetGraphQLHandler.ts index 35d769be5e3..f877abc2abe 100644 --- a/packages/server/graphql/intranetGraphQLHandler.ts +++ b/packages/server/graphql/intranetGraphQLHandler.ts @@ -38,9 +38,7 @@ const intranetHttpGraphQLHandler = uWSAsyncHandler(async (res: HttpResponse, req isPrivate, isAdHoc: true }) - res.cork(() => { - res.writeHeader('content-type', 'application/json').end(JSON.stringify(result)) - }) + res.writeHeader('content-type', 'application/json').end(JSON.stringify(result)) } catch (e) { res.writeStatus('502').end() } diff --git a/packages/server/jiraImagesHandler.ts b/packages/server/jiraImagesHandler.ts index 9197d7d6a02..64d7c787b83 100644 --- a/packages/server/jiraImagesHandler.ts +++ b/packages/server/jiraImagesHandler.ts @@ -35,9 +35,7 @@ const servePlaceholderImage = async (res: HttpResponse) => { path.join(__dirname, jiraPlaceholder.slice(__webpack_public_path__.length)) ) } - res.cork(() => { - res.writeStatus('200').writeHeader('Content-Type', 'image/png').end(jiraPlaceholderBuffer) - }) + res.writeStatus('200').writeHeader('Content-Type', 'image/png').end(jiraPlaceholderBuffer) } const jiraImagesHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpRequest) => { @@ -53,12 +51,10 @@ const jiraImagesHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpReq return } - res.cork(() => { - res - .writeStatus('200') - .writeHeader('Content-Type', cachedImage.contentType) - .end(cachedImage.imageBuffer) - }) + res + .writeStatus('200') + .writeHeader('Content-Type', cachedImage.contentType) + .end(cachedImage.imageBuffer) }) export default jiraImagesHandler diff --git a/packages/server/pipeStreamOverResponse.ts b/packages/server/pipeStreamOverResponse.ts index ba7d18ba1d4..313d28e35b0 100644 --- a/packages/server/pipeStreamOverResponse.ts +++ b/packages/server/pipeStreamOverResponse.ts @@ -41,7 +41,9 @@ const pipeStreamOverResponse = ( .on('error', () => { if (!res.aborted) { - res.writeStatus('500').end() + res.cork(() => { + res.writeStatus('500').end() + }) } readStream.destroy() }) diff --git a/packages/server/safetyPatchRes.ts b/packages/server/safetyPatchRes.ts index d21ed31238e..d48e736b5cf 100644 --- a/packages/server/safetyPatchRes.ts +++ b/packages/server/safetyPatchRes.ts @@ -1,5 +1,7 @@ import {HttpResponse, RecognizedString} from 'uWebSockets.js' +type Header = [key: RecognizedString, value: RecognizedString] + const safetyPatchRes = (res: HttpResponse) => { if (res._end) { throw new Error('already patched') @@ -17,6 +19,20 @@ const safetyPatchRes = (res: HttpResponse) => { return res } + // Cache writes until `.end()` gets called. Then flush + res.status = '' + res.headers = [] as Header[] + + const flush = (thunk: () => T) => { + return res._cork(() => { + if (res.status) res._writeStatus(res.status) + res.headers.forEach((header: Header) => { + res._writeHeader(...header) + }) + return thunk() + }) + } + res._end = res.end res.end = (body?: RecognizedString) => { if (res.done) { @@ -24,7 +40,7 @@ const safetyPatchRes = (res: HttpResponse) => { } if (res.done || res.aborted) return res res.done = true - return res._end(body) + return flush(() => res._end(body)) } res._close = res.close @@ -38,12 +54,8 @@ const safetyPatchRes = (res: HttpResponse) => { } res._cork = res.cork - res.cork = (cb: () => void) => { - if (res.done) { - console.warn(`uWS: Called cork after done`) - } - if (res.done || res.aborted) return res - return res._cork(cb) + res.cork = () => { + throw new Error('safetyPatchRes applies the cork for you, do not call directly') } res._tryEnd = res.tryEnd @@ -52,7 +64,7 @@ const safetyPatchRes = (res: HttpResponse) => { console.warn(`uWS: Called tryEnd after done`) } if (res.done || res.aborted) return [true, true] - return res._tryEnd(fullBodyOrChunk, totalSize) + return flush(() => res._tryEnd(fullBodyOrChunk, totalSize)) } res._write = res.write @@ -69,8 +81,8 @@ const safetyPatchRes = (res: HttpResponse) => { if (res.done) { console.warn(`uWS: Called writeHeader after done`) } - if (res.done || res.aborted) return res - return res._writeHeader(key, value) + res.headers.push([key, value]) + return res } res._writeStatus = res.writeStatus @@ -78,8 +90,8 @@ const safetyPatchRes = (res: HttpResponse) => { if (res.done) { console.error(`uWS: Called writeStatus after done ${status}`) } - if (res.done || res.aborted) return res - return res._writeStatus(status) + res.status = status + return res } res._upgrade = res.upgrade @@ -88,13 +100,15 @@ const safetyPatchRes = (res: HttpResponse) => { console.error(`uWS: Called upgrade after done`) } if (res.done || res.aborted) return - return res._upgrade(...args) + return res._cork(() => { + res._upgrade(...args) + }) } res._getRemoteAddressAsText = res.getRemoteAddressAsText res.getRemoteAddressAsText = () => { if (res.done) { - console.error(`uWS: Called upgrade after done`) + console.error(`uWS: Called getRemoteAddressAsText after done`) } if (res.done || res.aborted) return Buffer.from('') return res._getRemoteAddressAsText() diff --git a/packages/server/socketHandlers/handleUpgrade.ts b/packages/server/socketHandlers/handleUpgrade.ts index 7eeb9668685..07874d2783d 100644 --- a/packages/server/socketHandlers/handleUpgrade.ts +++ b/packages/server/socketHandlers/handleUpgrade.ts @@ -1,5 +1,6 @@ import {WebSocketBehavior} from 'uWebSockets.js' import {TrebuchetCloseReason} from '../../client/types/constEnums' +import safetyPatchRes from '../safetyPatchRes' import {isAuthenticated} from '../utils/authorization' import checkBlacklistJWT from '../utils/checkBlacklistJWT' import getQueryToken from '../utils/getQueryToken' @@ -7,6 +8,7 @@ import sendToSentry from '../utils/sendToSentry' import uwsGetIP from '../utils/uwsGetIP' const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, context) => { + safetyPatchRes(res) const protocol = req.getHeader('sec-websocket-protocol') if (protocol !== 'trebuchet-ws') { sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) @@ -19,9 +21,6 @@ const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, conte res.writeStatus('401').end() return } - res.onAborted(() => { - res.aborted = true - }) const key = req.getHeader('sec-websocket-key') const extensions = req.getHeader('sec-websocket-extensions') @@ -29,12 +28,10 @@ const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, conte const {sub: userId, iat} = authToken // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) - if (res.aborted) return if (isBlacklistedJWT) { res.writeStatus('401').end(TrebuchetCloseReason.EXPIRED_SESSION) return } - res.upgrade({ip, authToken}, key, protocol, extensions, context) } diff --git a/packages/server/sse/whm.js b/packages/server/sse/whm.js deleted file mode 100644 index 7994de24ee5..00000000000 --- a/packages/server/sse/whm.js +++ /dev/null @@ -1,163 +0,0 @@ -module.exports = webpackHotMiddleware - -function webpackHotMiddleware(compiler, opts) { - opts = opts || {} - opts.log = - typeof opts.log == 'undefined' ? console.log.bind(console) : opts.log - opts.path = opts.path || '/__webpack_hmr' - opts.heartbeat = opts.heartbeat || 10 * 1000 - - var eventStream = createEventStream(opts.heartbeat) - var latestStats = null - var closed = false - - if (compiler.hooks) { - compiler.hooks.invalid.tap('webpack-hot-middleware', onInvalid) - compiler.hooks.done.tap('webpack-hot-middleware', onDone) - } else { - compiler.plugin('invalid', onInvalid) - compiler.plugin('done', onDone) - } - function onInvalid() { - if (closed) return - latestStats = null - if (opts.log) opts.log('webpack building...') - eventStream.publish({action: 'building'}) - } - function onDone(statsResult) { - if (closed) return - // Keep hold of latest stats so they can be propagated to new clients - latestStats = statsResult - publishStats('built', latestStats, eventStream, opts.log) - } - var middleware = function (req, res, next) { - if (closed) return next() - if (!req.url.includes(opts.path)) return next() - // if (!pathMatch(req.url, opts.path)) return next() - eventStream.handler(req, res) - if (latestStats) { - // Explicitly not passing in `log` fn as we don't want to log again on - // the server - publishStats('sync', latestStats, eventStream) - } - } - middleware.publish = function (payload) { - if (closed) return - eventStream.publish(payload) - } - middleware.close = function () { - if (closed) return - // Can't remove compiler plugins, so we just set a flag and noop if closed - // https://github.com/webpack/tapable/issues/32#issuecomment-350644466 - closed = true - eventStream.close() - eventStream = null - } - return middleware -} - -function createEventStream(heartbeat) { - const clients = [] - function everyClient(fn) { - clients.forEach((client) => { - if (client.done) { - clients.splice(clients.indexOf(client), 1) - } else { - fn(client) - } - }) - } - var interval = setInterval(function heartbeatTick() { - everyClient(function (client) { - client.tryEnd('data: \uD83D\uDC93\n\n', 1e8) - }) - }, heartbeat).unref() - return { - close: function () { - clearInterval(interval) - everyClient(function (client) { - client.end() - }) - clients.length = 0 - }, - handler: function (_req, res) { - if (res.done) return - res.writeHeader('Access-Control-Allow-Origin', '*') - res.writeHeader('Content-Type', 'text/event-stream;charset=utf-8') - res.writeHeader('Connection', 'keep-alive') - res.writeHeader('Cache-Control', 'no-cache, no-transform') - res.writeHeader('X-Accel-Buffering', 'no') - res.tryEnd(`retry: 1000\n`, 1e8) - res.tryEnd('\n', 1e8) - clients.push(res) - res.onAborted(() => { - clients.splice(clients.indexOf(res), 1) - }) - }, - publish: function (payload) { - everyClient(function (client) { - client.tryEnd('data: ' + JSON.stringify(payload) + '\n\n', 1e8) - }) - }, - } -} - -function publishStats(action, statsResult, eventStream, log) { - var stats = statsResult.toJson({ - all: false, - cached: true, - children: true, - modules: true, - timings: true, - hash: true, - }) - // For multi-compiler, stats will be an object with a 'children' array of stats - var bundles = extractBundles(stats) - bundles.forEach(function (stats) { - var name = stats.name || '' - - // Fallback to compilation name in case of 1 bundle (if it exists) - if (bundles.length === 1 && !name && statsResult.compilation) { - name = statsResult.compilation.name || '' - } - - if (log) { - log( - 'webpack built ' + - (name ? name + ' ' : '') + - stats.hash + - ' in ' + - stats.time + - 'ms' - ) - } - eventStream.publish({ - name: name, - action: action, - time: stats.time, - hash: stats.hash, - warnings: stats.warnings || [], - errors: stats.errors || [], - modules: buildModuleMap(stats.modules), - }) - }) -} - -function extractBundles(stats) { - // Stats has modules, single bundle - if (stats.modules) return [stats] - - // Stats has children, multiple bundles - if (stats.children && stats.children.length) return stats.children - - // Not sure, assume single - return [stats] -} - -function buildModuleMap(modules) { - var map = {} - modules.forEach(function (module) { - map[module.id] = module.name - }) - return map -} diff --git a/packages/server/utils/SAMLHandler.ts b/packages/server/utils/SAMLHandler.ts index c4cfc4a726c..0b3b2b67254 100644 --- a/packages/server/utils/SAMLHandler.ts +++ b/packages/server/utils/SAMLHandler.ts @@ -47,15 +47,13 @@ const SAMLHandler = uWSAsyncHandler(async (res: HttpResponse, req: HttpRequest) redirectOnError(res, message) return } - res.cork(() => { - res - .writeStatus('302') - .writeHeader( - 'location', - `/saml-redirect?userId=${userId}&token=${authToken}&isNewUser=${isNewUser}&isPatient0=${user.isPatient0}` - ) - .end() - }) + res + .writeStatus('302') + .writeHeader( + 'location', + `/saml-redirect?userId=${userId}&token=${authToken}&isNewUser=${isNewUser}&isPatient0=${user.isPatient0}` + ) + .end() }) export default SAMLHandler diff --git a/packages/server/utils/serveStatic.ts b/packages/server/utils/serveStatic.ts index 41bf4a090d8..2448123077b 100644 --- a/packages/server/utils/serveStatic.ts +++ b/packages/server/utils/serveStatic.ts @@ -29,14 +29,12 @@ const serveStatic = (res: HttpResponse, fileName: string, sendCompressed?: boole if (!meta) return false const {size, pathname, brotliFile, file, type} = meta if (file) { - res.cork(() => { - res.writeHeader('content-type', type) - if (__PRODUCTION__ && sendCompressed && brotliFile) { - res.writeHeader('content-encoding', 'br').end(brotliFile) - } else { - res.end(file) - } - }) + res.writeHeader('content-type', type) + if (__PRODUCTION__ && sendCompressed && brotliFile) { + res.writeHeader('content-encoding', 'br').end(brotliFile) + } else { + res.end(file) + } return true } res.writeHeader('content-type', type) From 53132c8562af68ccc58e9f1225327cbe8eb50aa3 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 4 Dec 2023 13:50:02 -0800 Subject: [PATCH 3/5] chore(dx): allow any branch with hotfix prefix to build Signed-off-by: Matt Krick --- .github/workflows/release-please.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ec769544ae4..0ea9c1ee005 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -2,7 +2,7 @@ on: push: branches: - master - - hotfix-* + - hotfix* name: release-please jobs: release-please: @@ -19,4 +19,4 @@ jobs: command: manifest default-branch: ${{ github.ref_name}} release-type: node - token: ${{ steps.generate_token.outputs.token }} \ No newline at end of file + token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf585472b50..084909c56f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ on: pull_request: branches: - master - - hotfix-* + - hotfix* types: [closed] jobs: release: From 463e0dc38503c935514c4b2c9baede94962aef44 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 4 Dec 2023 14:04:08 -0800 Subject: [PATCH 4/5] fix: double star glob to match / Signed-off-by: Matt Krick --- .github/workflows/release-please.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0ea9c1ee005..6bf22db8d7d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -2,7 +2,7 @@ on: push: branches: - master - - hotfix* + - hotfix** name: release-please jobs: release-please: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 084909c56f4..3daa63ee4f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ on: pull_request: branches: - master - - hotfix* + - hotfix** types: [closed] jobs: release: From 4a4db46c0af7e920253181e465ffece386a1054e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:05:55 +0000 Subject: [PATCH 5/5] chore(release): release v7.10.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 26 +++++++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0b90b1438ca..72e7a5fa240 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.9.0" + ".": "7.10.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d08f005140..5d7df7b57de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.10.0](https://github.com/ParabolInc/parabol/compare/v7.9.0...v7.10.0) (2023-12-04) + + +### Added + +* add tooltip to activity library card ([#9236](https://github.com/ParabolInc/parabol/issues/9236)) ([f8511b2](https://github.com/ParabolInc/parabol/commit/f8511b21cac91d7bf3cace828ceb337848bb3ee8)) +* gcal invite all by default ([#9260](https://github.com/ParabolInc/parabol/issues/9260)) ([1e71688](https://github.com/ParabolInc/parabol/commit/1e71688e0d711e945ed373b2448b58c067e2f4b3)) +* remove gcal flag ([#9251](https://github.com/ParabolInc/parabol/issues/9251)) ([9961e63](https://github.com/ParabolInc/parabol/commit/9961e6305287040179e6a2382f6ad6c9c3035d35)) +* update activity library card UI ([#9168](https://github.com/ParabolInc/parabol/issues/9168)) ([662ec2b](https://github.com/ParabolInc/parabol/commit/662ec2bee8ecbb71ce2d6e5718450130eee8bcac)) + + +### Fixed + +* auto-cork, sse not working ([d486bc3](https://github.com/ParabolInc/parabol/commit/d486bc3ed1942b84183265a3e01204a113cb2fbf)) +* double star glob to match / ([463e0dc](https://github.com/ParabolInc/parabol/commit/463e0dc38503c935514c4b2c9baede94962aef44)) +* increases integration icon visibility ([#9164](https://github.com/ParabolInc/parabol/issues/9164)) ([b9bcd69](https://github.com/ParabolInc/parabol/commit/b9bcd6914a8940600a2a5ac886ba2cf64201aa43)) + + +### Changed + +* Cleanup Slack/Mattermost/MSTeams notifiers ([#9240](https://github.com/ParabolInc/parabol/issues/9240)) ([3bf4b81](https://github.com/ParabolInc/parabol/commit/3bf4b8102cd21a7a7130c028044d8c2251bfab14)) +* **dx:** allow any branch with hotfix prefix to build ([53132c8](https://github.com/ParabolInc/parabol/commit/53132c8562af68ccc58e9f1225327cbe8eb50aa3)) +* **dx:** allow any branch with hotfix prefix to build ([#9263](https://github.com/ParabolInc/parabol/issues/9263)) ([619c07c](https://github.com/ParabolInc/parabol/commit/619c07ce3c6eb884d3896a53deab210db2feef84)) +* **env-file:** ununsed variables removed ([#9249](https://github.com/ParabolInc/parabol/issues/9249)) ([c155c12](https://github.com/ParabolInc/parabol/commit/c155c12486d3e07384b280b79ec0a0e216343243)) +* **metrics:** add metrics to track search query in AL ([#9235](https://github.com/ParabolInc/parabol/issues/9235)) ([bfaccd8](https://github.com/ParabolInc/parabol/commit/bfaccd8154c7a4c4229adce8bf25e5ffc8d11ef0)) + ## [7.9.0](https://github.com/ParabolInc/parabol/compare/v7.8.1...v7.9.0) (2023-11-29) diff --git a/package.json b/package.json index 9531a4b9de4..228157b4a92 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.9.0", + "version": "7.10.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f82f59c4382..6e415df45c1 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.9.0", + "version": "7.10.0", "description": "A cron job scheduler", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/chronos#readme", @@ -25,6 +25,6 @@ }, "dependencies": { "cron": "^2.3.1", - "parabol-server": "7.9.0" + "parabol-server": "7.10.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7262c0589ae..885e1671eab 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.9.0", + "version": "7.10.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 21d7237d6cd..4dcfa5a125d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.9.0", + "version": "7.10.0", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -27,8 +27,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.9.0", - "parabol-server": "7.9.0", + "parabol-client": "7.10.0", + "parabol-server": "7.10.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 15142a08823..8cd1fa0714c 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -2,7 +2,7 @@ "name": "integration-tests", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.9.0", + "version": "7.10.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e686b72264d..70538edd626 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.9.0", + "version": "7.10.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.9.0", "oy-vey": "^0.11.0", - "parabol-client": "7.9.0", + "parabol-client": "7.10.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2",