@nuxt-customer-portal/planning 0.4.6 → 0.4.7

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.7
4
+
5
+ ### Patch Changes
6
+
7
+ - Keep Google Calendar settings recoverable when stored authorization expires, and offer reconnection after a rejected token refresh.
8
+ - @nuxt-customer-portal/core@0.4.7
9
+ - @nuxt-customer-portal/products@0.4.7
10
+ - @nuxt-customer-portal/ui@0.4.7
11
+
3
12
  ## 0.4.6
4
13
 
5
14
  ### Patch Changes
@@ -46,7 +46,23 @@ async function load() {
46
46
  busyCalendarIds: settings.busyCalendarIds,
47
47
  writeCalendarId: settings.writeCalendarId || ''
48
48
  })
49
- calendars.value = googleConnected.value ? await api.calendars() : []
49
+ if (googleConnected.value) {
50
+ try {
51
+ calendars.value = await api.calendars()
52
+ } catch (calendarError) {
53
+ const refreshedSettings = await api.provider().catch(() => null)
54
+ const connectionStillHealthy = refreshedSettings?.connections.some(
55
+ (connection) => connection.provider === 'google' && connection.healthy
56
+ )
57
+ if (connectionStillHealthy !== false) {
58
+ throw calendarError
59
+ }
60
+ googleConnected.value = false
61
+ calendars.value = []
62
+ }
63
+ } else {
64
+ calendars.value = []
65
+ }
50
66
  ready.value = true
51
67
  } catch {
52
68
  error.value = t('planning.loadError')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt-customer-portal/planning",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "license": "MIT",
5
5
  "homepage": "https://nuxt-customer-portal.com",
6
6
  "repository": {
@@ -40,9 +40,9 @@
40
40
  "drizzle-orm": "^0.45.2",
41
41
  "pg": "^8.22.0",
42
42
  "zod": "^4.4.3",
43
- "@nuxt-customer-portal/core": "^0.4.6",
44
- "@nuxt-customer-portal/products": "^0.4.6",
45
- "@nuxt-customer-portal/ui": "^0.4.6"
43
+ "@nuxt-customer-portal/core": "^0.4.7",
44
+ "@nuxt-customer-portal/ui": "^0.4.7",
45
+ "@nuxt-customer-portal/products": "^0.4.7"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "nuxt": "^4.5.1",
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  id: 'planning',
3
- version: '0.4.6',
3
+ version: '0.4.7',
4
4
  source: '@nuxt-customer-portal/planning',
5
5
  dependsOn: ['core', 'ui', 'products'],
6
6
  schema: './server/db/schema/planning.ts',
@@ -54,6 +54,14 @@ export interface MeetingAdapter {
54
54
  ): Promise<{ id: string; url: string }>
55
55
  remove(storeId: string, userId: string, id: string): Promise<void>
56
56
  }
57
+ class AuthorizationExchangeError extends Error {
58
+ constructor(
59
+ public readonly status: number,
60
+ message = `Calendar/meeting authorization failed (${status})`
61
+ ) {
62
+ super(message)
63
+ }
64
+ }
57
65
  export function oauthConfig(provider: 'google' | 'zoom') {
58
66
  const prefix = `PLANNING_${provider.toUpperCase()}`
59
67
  const clientId = process.env[`${prefix}_CLIENT_ID`],
@@ -84,7 +92,7 @@ export async function exchange(provider: 'google' | 'zoom', params: Record<strin
84
92
  }
85
93
  )
86
94
  if (!response.ok) {
87
- throw new Error(`Calendar/meeting authorization failed (${response.status})`)
95
+ throw new AuthorizationExchangeError(response.status)
88
96
  }
89
97
  const value = (await response.json()) as { access_token: string; refresh_token?: string; expires_in: number }
90
98
  if (!value.access_token || !value.expires_in) {
@@ -111,7 +119,21 @@ async function accessToken(storeId: string, userId: string, provider: 'google' |
111
119
  }
112
120
  let credentials = decrypt<Credentials>(connection.credentials)
113
121
  if (credentials.expiresAt < Date.now() + 60000) {
114
- credentials = await exchange(provider, { grant_type: 'refresh_token', refresh_token: credentials.refresh_token })
122
+ try {
123
+ credentials = await exchange(provider, {
124
+ grant_type: 'refresh_token',
125
+ refresh_token: credentials.refresh_token
126
+ })
127
+ } catch (error) {
128
+ if (error instanceof AuthorizationExchangeError && [400, 401].includes(error.status)) {
129
+ await rows(
130
+ 'UPDATE planning.connection SET healthy=false,error=$4 WHERE store_id=$1 AND user_id=$2 AND provider=$3',
131
+ [storeId, userId, provider, 'Authorization expired; reconnect'],
132
+ client
133
+ )
134
+ }
135
+ throw error
136
+ }
115
137
  await rows(
116
138
  'UPDATE planning.connection SET credentials=$4,error=NULL WHERE store_id=$1 AND user_id=$2 AND provider=$3',
117
139
  [storeId, userId, provider, encrypt(credentials)],