diff --git a/packages/frontend-2/components/projects/invite/Banner.vue b/packages/frontend-2/components/projects/invite/Banner.vue
index a3fb67ce5a..1a428ad4b4 100644
--- a/packages/frontend-2/components/projects/invite/Banner.vue
+++ b/packages/frontend-2/components/projects/invite/Banner.vue
@@ -1,5 +1,6 @@
+
diff --git a/packages/frontend-2/lib/core/configs/apollo.ts b/packages/frontend-2/lib/core/configs/apollo.ts
index bda2e39351..eaafc8018c 100644
--- a/packages/frontend-2/lib/core/configs/apollo.ts
+++ b/packages/frontend-2/lib/core/configs/apollo.ts
@@ -22,7 +22,7 @@ import { onError } from '@apollo/client/link/error'
import { useNavigateToLogin, loginRoute } from '~~/lib/common/helpers/route'
import { useAppErrorState } from '~~/lib/core/composables/appErrorState'
import { isInvalidAuth } from '~~/lib/common/helpers/graphql'
-import { omit } from 'lodash-es'
+import { isBoolean, omit } from 'lodash-es'
import { useRequestId } from '~/lib/core/composables/server'
const appName = 'frontend-2'
@@ -302,7 +302,10 @@ function createLink(params: {
'need a token to subscribe'
)
- const shouldSkip = !!res.operation.getContext().skipLoggingErrors
+ const skipLoggingErrors = res.operation.getContext().skipLoggingErrors
+ const shouldSkip = isBoolean(skipLoggingErrors)
+ ? skipLoggingErrors
+ : skipLoggingErrors?.(res)
if (!isSubTokenMissingError && !shouldSkip) {
const errMsg = res.networkError?.message || res.graphQLErrors?.[0]?.message
logger.error(
diff --git a/packages/frontend-2/pages/projects/[id]/index.vue b/packages/frontend-2/pages/projects/[id]/index.vue
index 6cf9797d35..81c4109fd3 100644
--- a/packages/frontend-2/pages/projects/[id]/index.vue
+++ b/packages/frontend-2/pages/projects/[id]/index.vue
@@ -1,7 +1,12 @@
-
+
@@ -60,23 +65,32 @@ definePageMeta({
})
const route = useRoute()
+const router = useRouter()
const projectId = computed(() => route.params.id as string)
+const shouldAutoAcceptInvite = computed(() => route.query.accept === 'true')
+const token = computed(() => route.query.token as Optional)
+
useGeneralProjectPageUpdateTracking({ projectId }, { notifyOnProjectUpdate: true })
const { result: projectPageResult } = useQuery(
projectPageQuery,
() => ({
id: projectId.value,
- token: (route.query.token as Optional) || null
+ token: token.value
}),
() => ({
// Custom error policy so that a failing invitedTeam resolver (due to access rights)
// doesn't kill the entire query
- errorPolicy: 'all'
+ errorPolicy: 'all',
+ context: {
+ skipLoggingErrors: (err) =>
+ err.graphQLErrors?.length === 1 &&
+ err.graphQLErrors.some((e) => !!e.path?.includes('invitedTeam'))
+ }
})
)
const project = computed(() => projectPageResult.value?.project)
-const invite = computed(() => projectPageResult.value?.projectInvite)
+const invite = computed(() => projectPageResult.value?.projectInvite || undefined)
const projectName = computed(() =>
project.value?.name.length ? project.value.name : ''
)
@@ -84,4 +98,12 @@ const projectName = computed(() =>
useHead({
title: projectName
})
+
+const onInviteAccepted = async (params: { accepted: boolean }) => {
+ if (params.accepted) {
+ await router.replace({
+ query: { ...route.query, accept: undefined, token: undefined }
+ })
+ }
+}
diff --git a/packages/frontend-2/plugins/001-logger.ts b/packages/frontend-2/plugins/001-logger.ts
index 5d41b296d5..f3e2a4aec5 100644
--- a/packages/frontend-2/plugins/001-logger.ts
+++ b/packages/frontend-2/plugins/001-logger.ts
@@ -105,13 +105,21 @@ export default defineNuxtPlugin(async (nuxtApp) => {
otherData.unshift(firstString)
}
+ const otherDataObjects = otherData.filter(isObjectLike)
+ const otherDataNonObjects = otherData.filter((o) => !isObjectLike(o))
+ const mergedOtherDataObject = Object.assign({}, ...otherDataObjects) as Record<
+ string,
+ unknown
+ >
+
seqLogger.emit({
timestamp: new Date(),
level: 'error',
- messageTemplate: 'Client-side error: {errorMessage}',
+ messageTemplate: 'Client-side error: {mainSeqErrorMessage}',
properties: {
- errorMessage,
- extraData: otherData,
+ mainSeqErrorMessage: errorMessage, // weird name to avoid collision with otherData
+ extraData: otherDataNonObjects,
+ ...mergedOtherDataObject,
...collectCoreInfo()
},
exception
diff --git a/packages/frontend-2/type-augmentations/apollo.d.ts b/packages/frontend-2/type-augmentations/apollo.d.ts
new file mode 100644
index 0000000000..c8fc0b81e4
--- /dev/null
+++ b/packages/frontend-2/type-augmentations/apollo.d.ts
@@ -0,0 +1,12 @@
+declare module '@apollo/client/core' {
+ interface DefaultContext {
+ /**
+ * Whether to skip logging errors caused in this operation
+ */
+ skipLoggingErrors?:
+ | boolean
+ | ((err: import('@apollo/client/link/error').ErrorResponse) => boolean)
+ }
+}
+
+export {}
diff --git a/packages/frontend/src/main/lib/stream/mixins/streamInviteMixin.ts b/packages/frontend/src/main/lib/stream/mixins/streamInviteMixin.ts
index 3d2d8b809b..ae31a1861c 100644
--- a/packages/frontend/src/main/lib/stream/mixins/streamInviteMixin.ts
+++ b/packages/frontend/src/main/lib/stream/mixins/streamInviteMixin.ts
@@ -28,6 +28,10 @@ export const UsersStreamInviteMixin = vueWithMixins(IsLoggedInMixin).extend({
inviteToken: {
type: String as PropType>,
default: null
+ },
+ autoAccept: {
+ type: Boolean,
+ default: false
}
},
data: () => ({
@@ -167,5 +171,10 @@ export const UsersStreamInviteMixin = vueWithMixins(IsLoggedInMixin).extend({
})
}
}
+ },
+ mounted() {
+ if (this.autoAccept) {
+ this.acceptInvite()
+ }
}
})
diff --git a/packages/frontend/src/main/pages/stream/TheStream.vue b/packages/frontend/src/main/pages/stream/TheStream.vue
index 987e695ad0..774b188877 100644
--- a/packages/frontend/src/main/pages/stream/TheStream.vue
+++ b/packages/frontend/src/main/pages/stream/TheStream.vue
@@ -12,6 +12,7 @@
v-if="hasInvite && !showInvitePlaceholder"
:stream-invite="streamInvite"
:invite-token="inviteToken"
+ :auto-accept="shouldAutoAcceptInvite"
@invite-used="onInviteClosed"
/>
@@ -26,6 +27,7 @@
v-if="showInvitePlaceholder"
:stream-invite="streamInvite"
:invite-token="inviteToken"
+ :auto-accept="shouldAutoAcceptInvite"
@invite-used="onInviteClosed"
/>
@@ -151,6 +153,9 @@ export default defineComponent({
inviteToken(): Nullable {
return getInviteTokenFromRoute(this.$route)
},
+ shouldAutoAcceptInvite(): boolean {
+ return this.$route.query.accept === 'true'
+ },
errorMsg(): MaybeFalsy {
return this.error?.message.replace('GraphQL error: ', '')
},
diff --git a/packages/server/modules/serverinvites/services/inviteCreationService.js b/packages/server/modules/serverinvites/services/inviteCreationService.js
index e4ea259143..907469557f 100644
--- a/packages/server/modules/serverinvites/services/inviteCreationService.js
+++ b/packages/server/modules/serverinvites/services/inviteCreationService.js
@@ -222,7 +222,7 @@ function buildInviteLink(invite) {
if (resourceTarget === 'streams') {
return new URL(
- `${getStreamRoute(resourceId)}?token=${token}`,
+ `${getStreamRoute(resourceId)}?token=${token}&accept=true`,
getFrontendOrigin()
).toString()
} else {
diff --git a/packages/server/modules/serverinvites/tests/invites.spec.js b/packages/server/modules/serverinvites/tests/invites.spec.js
index a2658d5c76..c48a45f688 100644
--- a/packages/server/modules/serverinvites/tests/invites.spec.js
+++ b/packages/server/modules/serverinvites/tests/invites.spec.js
@@ -38,7 +38,7 @@ async function cleanup() {
function getInviteTokenFromEmailParams(emailParams) {
const { text } = emailParams
- const [, inviteId] = text.match(/\?token=(.*)\s/i)
+ const [, inviteId] = text.match(/\?token=(.*?)(\s|&)/i)
return inviteId
}