Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integrations/dropbox/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { actions, configuration, configurations, entities, secrets, states } fro
export default new sdk.IntegrationDefinition({
name: 'dropbox',
title: 'Dropbox',
version: '2.0.1',
version: '2.0.2',
description: 'Manage your files and folders effortlessly.',
readme: 'hub.md',
icon: 'icon.svg',
Expand Down
22 changes: 21 additions & 1 deletion integrations/dropbox/src/webhook-events/oauth/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ export const handler = async (props: bp.HandlerProps) => {
return response
}

const _startHandler: WizardHandler = (props) => {
const _startHandler: WizardHandler = async (props) => {
const { responses } = props

// When nothing is connected yet there's nothing to reset, so skip the
// confirmation and go straight to authorizing with Dropbox.
if (!(await _isAlreadyConnected(props))) {
return _redirectToDropboxHandler(props)
}

return responses.displayButtons({
pageTitle: 'Reset Configuration',
htmlOrMarkdownPageContents:
Expand All @@ -38,6 +45,19 @@ const _startHandler: WizardHandler = (props) => {
})
}

const _isAlreadyConnected = async ({ client, ctx }: bp.HandlerProps): Promise<boolean> => {
try {
const result = await client.getState({
id: ctx.integrationId,
type: 'integration',
name: 'authorization',
})
return Boolean(result?.state?.payload?.refreshToken)
} catch {
return false
}
}

const _redirectToDropboxHandler: WizardHandler = async (props) => {
const { responses, ctx } = props
const clientId = getOAuthClientId({ ctx })
Expand Down
2 changes: 1 addition & 1 deletion integrations/messenger/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { actions } from './definitions/actions'
import { messages } from './definitions/channels/channel/messages'

export const INTEGRATION_NAME = 'messenger'
export const INTEGRATION_VERSION = '5.1.8'
export const INTEGRATION_VERSION = '5.1.9'

const commonConfigSchema = z.object({
downloadMedia: z
Expand Down
19 changes: 18 additions & 1 deletion integrations/messenger/src/webhook/handlers/oauth/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ export const handler = async (props: bp.HandlerProps) => {
return response
}

const _startHandler: WizardHandler = ({ responses }) => {
const _startHandler: WizardHandler = async (props) => {
const { responses } = props

// When nothing is connected yet there's nothing to reset, so skip the
// confirmation and go straight to the next step.
if (!(await _isAlreadyConnected(props))) {
return _resetHandler(props)
}

return responses.displayButtons({
pageTitle: 'Reset Configuration',
htmlOrMarkdownPageContents: `
Expand All @@ -43,6 +51,15 @@ const _startHandler: WizardHandler = ({ responses }) => {
})
}

const _isAlreadyConnected = async ({ client, ctx }: bp.HandlerProps): Promise<boolean> => {
try {
const result = await client.getState({ type: 'integration', name: 'oauth', id: ctx.integrationId })
return Boolean(result?.state?.payload?.pageToken)
} catch {
return false
}
}

const _resetHandler: WizardHandler = async ({ responses, client, ctx }) => {
await client.setState({
type: 'integration',
Expand Down
2 changes: 1 addition & 1 deletion integrations/whatsapp/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const defaultBotPhoneNumberId = {
}

export const INTEGRATION_NAME = 'whatsapp'
export const INTEGRATION_VERSION = '4.16.0'
export const INTEGRATION_VERSION = '4.16.1'
export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: INTEGRATION_VERSION,
Expand Down
16 changes: 16 additions & 0 deletions integrations/whatsapp/src/webhook/handlers/oauth/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export const handler = async (props: bp.HandlerProps): Promise<Response> => {

const _startConfirmHandler: WizardHandler = async (props) => {
const { responses } = props

// When nothing is connected yet there's nothing to reset, so skip the
// confirmation and go straight to the next step.
if (!(await _isAlreadyConnected(props))) {
return _setupHandler(props)
}

return responses.displayButtons({
pageTitle: 'Reset Configuration',
htmlOrMarkdownPageContents:
Expand All @@ -65,6 +72,15 @@ const _startConfirmHandler: WizardHandler = async (props) => {
})
}

const _isAlreadyConnected = async ({ client, ctx }: bp.HandlerProps): Promise<boolean> => {
try {
const { accessToken } = await _getCredentialsState(client, ctx)
return Boolean(accessToken)
} catch {
return false
}
}

const _setupHandler: WizardHandler = async (props) => {
const { responses, client, ctx } = props
// Clean current state to start a fresh wizard
Expand Down
2 changes: 1 addition & 1 deletion integrations/zendesk/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { actions, events, configuration, channels, states, user } from './src/de
export default new sdk.IntegrationDefinition({
name: 'zendesk',
title: 'Zendesk',
version: '3.1.4',
version: '3.1.5',
icon: 'icon.svg',
description:
'Optimize your support workflow. Trigger workflows from ticket updates as well as manage tickets, access conversations, and engage with customers.',
Expand Down
18 changes: 17 additions & 1 deletion integrations/zendesk/src/oauth/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ export const handler = async (props: bp.HandlerProps) => {
return response
}

const _startHandler: WizardHandler = (props) => {
const _startHandler: WizardHandler = async (props) => {
const { responses } = props

// When nothing is connected yet there's nothing to reset, so skip the
// confirmation and go straight to the next step.
if (!(await _isAlreadyConnected(props))) {
return _getSubdomain(props)
}

return responses.displayButtons({
pageTitle: 'Reset Configuration',
htmlOrMarkdownPageContents:
Expand All @@ -61,6 +68,15 @@ const _startHandler: WizardHandler = (props) => {
})
}

const _isAlreadyConnected = async ({ client, ctx }: bp.HandlerProps): Promise<boolean> => {
try {
const result = await client.getState({ type: 'integration', name: 'credentials', id: ctx.integrationId })
return Boolean(result?.state?.payload?.accessToken)
} catch {
return false
}
}

const _getSubdomain: WizardHandler = async (props) => {
const { responses } = props
return responses.displayInput({
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@apidevtools/json-schema-ref-parser": "^11.7.0",
"@botpress/chat": "0.5.5",
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.1",
"@botpress/sdk": "6.11.2",
"@bpinternal/const": "^0.1.0",
"@bpinternal/tunnel": "^0.1.1",
"@bpinternal/verel": "^0.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.1"
"@botpress/sdk": "6.11.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.1"
"@botpress/sdk": "6.11.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"private": true,
"dependencies": {
"@botpress/sdk": "6.11.1"
"@botpress/sdk": "6.11.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/hello-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.1"
"@botpress/sdk": "6.11.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/webhook-message/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.1",
"@botpress/sdk": "6.11.2",
"axios": "^1.6.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/sdk",
"version": "6.11.1",
"version": "6.11.2",
"description": "Botpress SDK",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
95 changes: 95 additions & 0 deletions packages/sdk/src/base-logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { describe, it, vi, afterEach } from 'vitest'
import { BaseLogger, type IssueLogEvent } from './base-logger'
import { IntegrationLogger } from './integration/server/integration-logger'

// A minimal concrete subclass for testing BaseLogger directly:
class TestLogger extends BaseLogger<object> {
public constructor(options: object = {}) {
super(options)
}

public override with(_options: object) {
return new TestLogger({ ...this.defaultOptions, ..._options })
}
}

const MOCK_ISSUE = {
type: 'issue',
code: 'TEST_CODE',
category: 'other',
title: 'Test issue',
description: 'A test issue description',
data: {},
groupBy: [],
} as const satisfies IssueLogEvent

afterEach(() => vi.restoreAllMocks())

describe.sequential('BaseLogger.issue()', () => {
it('emits JSON with no extra keys when context is empty', ({ expect }) => {
// Arrange
const logger = new TestLogger()
const spy = vi.spyOn(console, 'info').mockImplementation(() => {})

// Act
logger.issue(MOCK_ISSUE)

// Assert
const emitted = JSON.parse(spy.mock.calls[0]![0] as string)
expect(emitted).toEqual(MOCK_ISSUE)
})

it('emits the exact substring "type":"issue" (no spaces in JSON)', ({ expect }) => {
// Arrange
const logger = new TestLogger()
const spy = vi.spyOn(console, 'info').mockImplementation(() => {})

// Act
logger.issue(MOCK_ISSUE)

// Assert
const raw = spy.mock.calls[0]![0] as string
expect(raw).toContain('"type":"issue"')
})
})

describe.sequential('IntegrationLogger.issue(): with identity options', () => {
it('includes botId, integrationId, and integrationAlias at the top level', ({ expect }) => {
// Arrange
const logger = new IntegrationLogger({
botId: 'bot-123',
integrationId: 'intg-456',
integrationAlias: 'myIntegration',
})
const spy = vi.spyOn(console, 'info').mockImplementation(() => {})

// Act
logger.issue(MOCK_ISSUE)

// Assert
const emitted = JSON.parse(spy.mock.calls[0]![0] as string)
expect(emitted.botId).toBe('bot-123')
expect(emitted.integrationId).toBe('intg-456')
expect(emitted.integrationAlias).toBe('myIntegration')
expect(emitted.type).toBe('issue')
expect(emitted.code).toBe(MOCK_ISSUE.code)
})
})

describe.sequential('IntegrationLogger.issue(): without identity options', () => {
it('emits exactly the original args keys (no extra keys, none undefined)', ({ expect }) => {
// Arrange
const logger = new IntegrationLogger()
const spy = vi.spyOn(console, 'info').mockImplementation(() => {})

// Act
logger.issue(MOCK_ISSUE)

// Assert
const emitted = JSON.parse(spy.mock.calls[0]![0] as string)
const emittedKeys = Object.keys(emitted).sort()
const expectedKeys = Object.keys(MOCK_ISSUE).sort()
expect(emittedKeys).toEqual(expectedKeys)
expect(Object.values(emitted).every((v) => v !== undefined)).toBe(true)
})
})
10 changes: 9 additions & 1 deletion packages/sdk/src/base-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ export abstract class BaseLogger<TOptions extends object> {
}

public issue(args: IssueLogEvent) {
console.info(JSON.stringify(args))
console.info(JSON.stringify({ ...args, ...this.getIssueContext() }))
}

/**
* Identity fields merged into every issue line so downstream ingestion can
* attribute and validate the issue without out-of-band context.
*/
protected getIssueContext(): Record<string, string> {
return {}
}

private _log(level: LogLevel, args: Parameters<typeof console.info>) {
Expand Down
9 changes: 9 additions & 0 deletions packages/sdk/src/integration/server/integration-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,13 @@ export class IntegrationLogger extends BaseLogger<IntegrationLogOptions> {
options: this.defaultOptions,
})
}

protected override getIssueContext(): Record<string, string> {
const { botId, integrationId, integrationAlias } = this.defaultOptions
return {
...(botId && { botId }),
...(integrationId && { integrationId }),
...(integrationAlias && { integrationAlias }),
}
}
}
Loading
Loading