Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,36 @@
The Activepieces Enterprise license (the “Enterprise License”)
Copyright (c) 2022-2023 Activepieces Inc.
With regard to the Activepieces Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Activepieces Subscription Terms of Service, available
at https://activepieces.com/terms (the “Enterprise Terms”), or other
agreement governing the use of the Software, as agreed by you and Activepieces,
and otherwise have a valid Activepieces Enterprise license for the
correct number of user seats. Subject to the foregoing sentence, you are free to
modify this Software and publish patches to the Software. You agree that Activepieces
and/or its licensors (as applicable) retain all right, title and interest in and
to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Activepieces Enterprise license for the correct
number of user seats. Notwithstanding the foregoing, you may copy and modify
the Software for development and testing purposes, without requiring a
subscription. You agree that Activepieces and/or its licensors (as applicable) retain
all right, title and interest in and to all such modifications. You are not
granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Activepieces Software, those
components are licensed under the original license provided by the owner of the
applicable component.

View File

@@ -0,0 +1,18 @@
{
"extends": ["../../server/api/.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# ee-shared
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build ee-shared` to build the library.

View File

@@ -0,0 +1,5 @@
{
"name": "@activepieces/ee-shared",
"version": "0.0.11",
"type": "commonjs"
}

View File

@@ -0,0 +1,23 @@
{
"name": "ee-shared",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/ee/shared/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/ee/shared",
"main": "packages/ee/shared/src/index.ts",
"tsConfig": "packages/ee/shared/tsconfig.lib.json",
"assets": ["packages/ee/shared/*.md"]
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}

View File

@@ -0,0 +1,18 @@
export * from './lib/billing'
export * from './lib/audit-events'
export * from './lib/git-repo'
export * from './lib/api-key'
export * from './lib/billing'
export * from './lib/project/project-requests'
export * from './lib/custom-domains'
export * from './lib/project-members/project-member-request'
export * from './lib/project-members/project-member'
export * from './lib/template'
export * from './lib/product-embed/app-credentials/index'
export * from './lib/product-embed/connection-keys/index'
export * from './lib/signing-key'
export * from './lib/managed-authn'
export * from './lib/oauth-apps'
export * from './lib/otp'
export * from './lib/authn'
export * from './lib/alerts'

View File

@@ -0,0 +1,16 @@
import { ApId, BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export enum AlertChannel {
EMAIL = 'EMAIL',
}
export const Alert = Type.Object({
...BaseModelSchema,
projectId: ApId,
channel: Type.Enum(AlertChannel),
receiver: Type.String({}),
})
export type Alert = Static<typeof Alert>

View File

@@ -0,0 +1,18 @@
import { ApId } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
import { AlertChannel } from './alerts-dto'
export const ListAlertsParams = Type.Object({
projectId: ApId,
cursor: Type.Optional(Type.String()),
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
})
export type ListAlertsParams = Static<typeof ListAlertsParams>
export const CreateAlertParams = Type.Object({
projectId: ApId,
channel: Type.Enum(AlertChannel),
receiver: Type.String({}),
})
export type CreateAlertParams = Static<typeof CreateAlertParams>

View File

@@ -0,0 +1,2 @@
export * from './alerts-dto'
export * from './alerts-requests'

View File

@@ -0,0 +1,34 @@
import { ApId, BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export const ApiKey = Type.Object({
...BaseModelSchema,
platformId: ApId,
displayName: Type.String(),
hashedValue: Type.String(),
truncatedValue: Type.String(),
lastUsedAt: Type.Optional(Type.String()),
})
export type ApiKey = Static<typeof ApiKey>
export const ApiKeyResponseWithValue = Type.Composite([
Type.Omit(ApiKey, ['hashedValue']),
Type.Object({
value: Type.String(),
}),
])
export type ApiKeyResponseWithValue = Static<typeof ApiKeyResponseWithValue>
export const ApiKeyResponseWithoutValue = Type.Omit(ApiKey, ['hashedValue'])
export type ApiKeyResponseWithoutValue = Static<typeof ApiKeyResponseWithoutValue>
export const CreateApiKeyRequest = Type.Object({
displayName: Type.String(),
})
export type CreateApiKeyRequest = Static<typeof CreateApiKeyRequest>

View File

@@ -0,0 +1,384 @@
import {
AppConnectionWithoutSensitiveData,
BaseModelSchema,
Flow,
FlowOperationRequest,
FlowOperationType,
FlowRun,
FlowVersion,
Folder,
Project,
ProjectRelease,
ProjectRole,
User,
} from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
import { SigningKey } from '../signing-key'
export const ListAuditEventsRequest = Type.Object({
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String()),
action: Type.Optional(Type.Array(Type.String())),
projectId: Type.Optional(Type.Array(Type.String())),
userId: Type.Optional(Type.String()),
createdBefore: Type.Optional(Type.String()),
createdAfter: Type.Optional(Type.String()),
})
export type ListAuditEventsRequest = Static<typeof ListAuditEventsRequest>
const UserMeta = Type.Pick(User, ['email', 'id', 'firstName', 'lastName'])
export enum ApplicationEventName {
FLOW_CREATED = 'flow.created',
FLOW_DELETED = 'flow.deleted',
FLOW_UPDATED = 'flow.updated',
FLOW_RUN_RESUMED = 'flow.run.resumed',
FLOW_RUN_STARTED = 'flow.run.started',
FLOW_RUN_FINISHED = 'flow.run.finished',
FOLDER_CREATED = 'folder.created',
FOLDER_UPDATED = 'folder.updated',
FOLDER_DELETED = 'folder.deleted',
CONNECTION_UPSERTED = 'connection.upserted',
CONNECTION_DELETED = 'connection.deleted',
USER_SIGNED_UP = 'user.signed.up',
USER_SIGNED_IN = 'user.signed.in',
USER_PASSWORD_RESET = 'user.password.reset',
USER_EMAIL_VERIFIED = 'user.email.verified',
SIGNING_KEY_CREATED = 'signing.key.created',
PROJECT_ROLE_CREATED = 'project.role.created',
PROJECT_ROLE_DELETED = 'project.role.deleted',
PROJECT_ROLE_UPDATED = 'project.role.updated',
PROJECT_RELEASE_CREATED = 'project.release.created',
}
const BaseAuditEventProps = {
...BaseModelSchema,
platformId: Type.String(),
projectId: Type.Optional(Type.String()),
projectDisplayName: Type.Optional(Type.String()),
userId: Type.Optional(Type.String()),
userEmail: Type.Optional(Type.String()),
ip: Type.Optional(Type.String()),
}
export const ConnectionEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([
Type.Literal(ApplicationEventName.CONNECTION_DELETED),
Type.Literal(ApplicationEventName.CONNECTION_UPSERTED),
]),
data: Type.Object({
connection: Type.Pick(AppConnectionWithoutSensitiveData, [
'displayName',
'externalId',
'pieceName',
'status',
'type',
'id',
'created',
'updated',
]),
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type ConnectionEvent = Static<typeof ConnectionEvent>
export const FolderEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([
Type.Literal(ApplicationEventName.FOLDER_UPDATED),
Type.Literal(ApplicationEventName.FOLDER_CREATED),
Type.Literal(ApplicationEventName.FOLDER_DELETED),
]),
data: Type.Object({
folder: Type.Pick(Folder, ['id', 'displayName', 'created', 'updated']),
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type FolderEvent = Static<typeof FolderEvent>
export const FlowRunEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([
Type.Literal(ApplicationEventName.FLOW_RUN_STARTED),
Type.Literal(ApplicationEventName.FLOW_RUN_FINISHED),
Type.Literal(ApplicationEventName.FLOW_RUN_RESUMED),
]),
data: Type.Object({
flowRun: Type.Pick(FlowRun, [
'id',
'startTime',
'finishTime',
'duration',
'environment',
'flowId',
'flowVersionId',
'flowDisplayName',
'status',
]),
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type FlowRunEvent = Static<typeof FlowRunEvent>
export const FlowCreatedEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Literal(ApplicationEventName.FLOW_CREATED),
data: Type.Object({
flow: Type.Pick(Flow, ['id', 'created', 'updated']),
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type FlowCreatedEvent = Static<typeof FlowCreatedEvent>
export const FlowDeletedEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Literal(ApplicationEventName.FLOW_DELETED),
data: Type.Object({
flow: Type.Pick(Flow, ['id', 'created', 'updated']),
flowVersion: Type.Pick(FlowVersion, [
'id',
'displayName',
'flowId',
'created',
'updated',
]),
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type FlowDeletedEvent = Static<typeof FlowDeletedEvent>
export const FlowUpdatedEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Literal(ApplicationEventName.FLOW_UPDATED),
data: Type.Object({
flowVersion: Type.Pick(FlowVersion, [
'id',
'displayName',
'flowId',
'created',
'updated',
]),
request: FlowOperationRequest,
project: Type.Optional(Type.Pick(Project, ['displayName'])),
}),
})
export type FlowUpdatedEvent = Static<typeof FlowUpdatedEvent>
export const AuthenticationEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([
Type.Literal(ApplicationEventName.USER_SIGNED_IN),
Type.Literal(ApplicationEventName.USER_PASSWORD_RESET),
Type.Literal(ApplicationEventName.USER_EMAIL_VERIFIED),
]),
data: Type.Object({
user: Type.Optional(UserMeta),
}),
})
export type AuthenticationEvent = Static<typeof AuthenticationEvent>
export const SignUpEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Literal(ApplicationEventName.USER_SIGNED_UP),
data: Type.Object({
source: Type.Union([
Type.Literal('credentials'),
Type.Literal('sso'),
Type.Literal('managed'),
]),
user: Type.Optional(UserMeta),
}),
})
export type SignUpEvent = Static<typeof SignUpEvent>
export const SigningKeyEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([Type.Literal(ApplicationEventName.SIGNING_KEY_CREATED)]),
data: Type.Object({
signingKey: Type.Pick(SigningKey, [
'id',
'created',
'updated',
'displayName',
]),
}),
})
export type SigningKeyEvent = Static<typeof SigningKeyEvent>
export const ProjectRoleEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Union([
Type.Literal(ApplicationEventName.PROJECT_ROLE_CREATED),
Type.Literal(ApplicationEventName.PROJECT_ROLE_UPDATED),
Type.Literal(ApplicationEventName.PROJECT_ROLE_DELETED),
]),
data: Type.Object({
projectRole: Type.Pick(ProjectRole, [
'id',
'created',
'updated',
'name',
'permissions',
'platformId',
]),
}),
})
export type ProjectRoleEvent = Static<typeof ProjectRoleEvent>
export const ProjectReleaseEvent = Type.Object({
...BaseAuditEventProps,
action: Type.Literal(ApplicationEventName.PROJECT_RELEASE_CREATED),
data: Type.Object({
release: Type.Pick(ProjectRelease, ['name', 'description', 'type', 'projectId', 'importedByUser']),
}),
})
export type ProjectReleaseEvent = Static<typeof ProjectReleaseEvent>
export const ApplicationEvent = Type.Union([
ConnectionEvent,
FlowCreatedEvent,
FlowDeletedEvent,
FlowUpdatedEvent,
FlowRunEvent,
AuthenticationEvent,
FolderEvent,
SignUpEvent,
SigningKeyEvent,
ProjectRoleEvent,
ProjectReleaseEvent,
])
export type ApplicationEvent = Static<typeof ApplicationEvent>
export function summarizeApplicationEvent(event: ApplicationEvent) {
switch (event.action) {
case ApplicationEventName.FLOW_UPDATED: {
return convertUpdateActionToDetails(event)
}
case ApplicationEventName.FLOW_RUN_STARTED:
return `Flow run ${event.data.flowRun.id} is started`
case ApplicationEventName.FLOW_RUN_FINISHED: {
return `Flow run ${event.data.flowRun.id} is finished`
}
case ApplicationEventName.FLOW_RUN_RESUMED: {
return `Flow run ${event.data.flowRun.id} is resumed`
}
case ApplicationEventName.FLOW_CREATED:
return `Flow ${event.data.flow.id} is created`
case ApplicationEventName.FLOW_DELETED:
return `Flow ${event.data.flow.id} (${event.data.flowVersion.displayName}) is deleted`
case ApplicationEventName.FOLDER_CREATED:
return `${event.data.folder.displayName} is created`
case ApplicationEventName.FOLDER_UPDATED:
return `${event.data.folder.displayName} is updated`
case ApplicationEventName.FOLDER_DELETED:
return `${event.data.folder.displayName} is deleted`
case ApplicationEventName.CONNECTION_UPSERTED:
return `${event.data.connection.displayName} (${event.data.connection.externalId}) is updated`
case ApplicationEventName.CONNECTION_DELETED:
return `${event.data.connection.displayName} (${event.data.connection.externalId}) is deleted`
case ApplicationEventName.USER_SIGNED_IN:
return `User ${event.userEmail} signed in`
case ApplicationEventName.USER_PASSWORD_RESET:
return `User ${event.userEmail} reset password`
case ApplicationEventName.USER_EMAIL_VERIFIED:
return `User ${event.userEmail} verified email`
case ApplicationEventName.USER_SIGNED_UP:
return `User ${event.userEmail} signed up using email from ${event.data.source}`
case ApplicationEventName.SIGNING_KEY_CREATED:
return `${event.data.signingKey.displayName} is created`
case ApplicationEventName.PROJECT_ROLE_CREATED:
return `${event.data.projectRole.name} is created`
case ApplicationEventName.PROJECT_ROLE_UPDATED:
return `${event.data.projectRole.name} is updated`
case ApplicationEventName.PROJECT_ROLE_DELETED:
return `${event.data.projectRole.name} is deleted`
case ApplicationEventName.PROJECT_RELEASE_CREATED:
return `${event.data.release.name} is created`
}
}
function convertUpdateActionToDetails(event: FlowUpdatedEvent) {
switch (event.data.request.type) {
case FlowOperationType.ADD_ACTION:
return `Added action "${event.data.request.request.action.displayName}" to "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.UPDATE_ACTION:
return `Updated action "${event.data.request.request.displayName}" in "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.DELETE_ACTION:
{
const request = event.data.request.request
const names = request.names
return `Deleted actions "${names.join(', ')}" from "${event.data.flowVersion.displayName}" Flow.`
}
case FlowOperationType.CHANGE_NAME:
return `Renamed flow "${event.data.flowVersion.displayName}" to "${event.data.request.request.displayName}".`
case FlowOperationType.LOCK_AND_PUBLISH:
return `Locked and published flow "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.USE_AS_DRAFT:
return `Unlocked and unpublished flow "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.MOVE_ACTION:
return `Moved action "${event.data.request.request.name}" to after "${event.data.request.request.newParentStep}".`
case FlowOperationType.LOCK_FLOW:
return `Locked flow "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.CHANGE_STATUS:
return `Changed status of flow "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.status}".`
case FlowOperationType.DUPLICATE_ACTION:
return `Duplicated action "${event.data.request.request.stepName}" in "${event.data.flowVersion.displayName}" Flow.`
case FlowOperationType.IMPORT_FLOW:
return `Imported flow in "${event.data.request.request.displayName}" Flow.`
case FlowOperationType.UPDATE_TRIGGER:
return `Updated trigger in "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.displayName}".`
case FlowOperationType.CHANGE_FOLDER:
return `Moved flow "${event.data.flowVersion.displayName}" to folder id ${event.data.request.request.folderId}.`
case FlowOperationType.DELETE_BRANCH: {
return `Deleted branch number ${
event.data.request.request.branchIndex + 1
} in flow "${event.data.flowVersion.displayName}" for the step "${
event.data.request.request.stepName
}".`
}
case FlowOperationType.SAVE_SAMPLE_DATA: {
return `Saved sample data for step "${event.data.request.request.stepName}" in flow "${event.data.flowVersion.displayName}".`
}
case FlowOperationType.DUPLICATE_BRANCH: {
return `Duplicated branch number ${
event.data.request.request.branchIndex + 1
} in flow "${event.data.flowVersion.displayName}" for the step "${
event.data.request.request.stepName
}".`
}
case FlowOperationType.ADD_BRANCH:
return `Added branch number ${
event.data.request.request.branchIndex + 1
} in flow "${event.data.flowVersion.displayName}" for the step "${
event.data.request.request.stepName
}".`
case FlowOperationType.SET_SKIP_ACTION:
{
const request = event.data.request.request
const names = request.names
return `Updated actions "${names.join(', ')}" in "${event.data.flowVersion.displayName}" Flow to skip.`
}
case FlowOperationType.UPDATE_METADATA:
return `Updated metadata for flow "${event.data.flowVersion.displayName}".`
case FlowOperationType.UPDATE_MINUTES_SAVED:
return `Updated minutes saved for flow "${event.data.flowVersion.displayName}".`
case FlowOperationType.MOVE_BRANCH:
return `Moved branch number ${
event.data.request.request.sourceBranchIndex + 1
} to ${
event.data.request.request.targetBranchIndex + 1
} in flow "${event.data.flowVersion.displayName}" for the step "${
event.data.request.request.stepName
}".`
}
}

View File

@@ -0,0 +1,82 @@
import { DefaultProjectRole, Permission } from '@activepieces/shared'
export const rolePermissions: Record<DefaultProjectRole, Permission[]> = {
[DefaultProjectRole.ADMIN]: [
Permission.READ_APP_CONNECTION,
Permission.WRITE_APP_CONNECTION,
Permission.READ_FLOW,
Permission.WRITE_FLOW,
Permission.UPDATE_FLOW_STATUS,
Permission.READ_PROJECT_MEMBER,
Permission.WRITE_PROJECT_MEMBER,
Permission.WRITE_INVITATION,
Permission.READ_INVITATION,
Permission.WRITE_PROJECT_RELEASE,
Permission.READ_PROJECT_RELEASE,
Permission.READ_RUN,
Permission.WRITE_RUN,
Permission.WRITE_ALERT,
Permission.READ_ALERT,
Permission.WRITE_PROJECT,
Permission.READ_PROJECT,
Permission.WRITE_FOLDER,
Permission.READ_FOLDER,
Permission.READ_TODOS,
Permission.WRITE_TODOS,
Permission.READ_TABLE,
Permission.WRITE_TABLE,
Permission.READ_MCP,
Permission.WRITE_MCP,
],
[DefaultProjectRole.EDITOR]: [
Permission.READ_APP_CONNECTION,
Permission.WRITE_APP_CONNECTION,
Permission.READ_FLOW,
Permission.WRITE_FLOW,
Permission.UPDATE_FLOW_STATUS,
Permission.READ_PROJECT_MEMBER,
Permission.READ_INVITATION,
Permission.WRITE_PROJECT_RELEASE,
Permission.READ_PROJECT_RELEASE,
Permission.READ_RUN,
Permission.WRITE_RUN,
Permission.READ_PROJECT,
Permission.WRITE_FOLDER,
Permission.READ_FOLDER,
Permission.READ_TODOS,
Permission.WRITE_TODOS,
Permission.READ_TABLE,
Permission.WRITE_TABLE,
Permission.READ_MCP,
Permission.WRITE_MCP,
],
[DefaultProjectRole.OPERATOR]: [
Permission.READ_APP_CONNECTION,
Permission.WRITE_APP_CONNECTION,
Permission.READ_FLOW,
Permission.UPDATE_FLOW_STATUS,
Permission.READ_PROJECT_MEMBER,
Permission.READ_INVITATION,
Permission.READ_PROJECT_RELEASE,
Permission.READ_RUN,
Permission.WRITE_RUN,
Permission.READ_PROJECT,
Permission.READ_FOLDER,
Permission.READ_TODOS,
Permission.WRITE_TODOS,
Permission.READ_TABLE,
Permission.READ_MCP,
],
[DefaultProjectRole.VIEWER]: [
Permission.READ_APP_CONNECTION,
Permission.READ_FLOW,
Permission.READ_PROJECT_MEMBER,
Permission.READ_INVITATION,
Permission.READ_PROJECT,
Permission.READ_RUN,
Permission.READ_FOLDER,
Permission.READ_TODOS,
Permission.READ_TABLE,
Permission.READ_MCP,
],
}

View File

@@ -0,0 +1 @@
export * from './requests'

View File

@@ -0,0 +1,25 @@
import { ApId, SignUpRequest } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export const VerifyEmailRequestBody = Type.Object({
identityId: ApId,
otp: Type.String(),
})
export type VerifyEmailRequestBody = Static<typeof VerifyEmailRequestBody>
export const ResetPasswordRequestBody = Type.Object({
identityId: ApId,
otp: Type.String(),
newPassword: Type.String(),
})
export type ResetPasswordRequestBody = Static<typeof ResetPasswordRequestBody>
export const SignUpAndAcceptRequestBody = Type.Composite([
Type.Omit(SignUpRequest, ['referringUserId', 'email']),
Type.Object({
invitationToken: Type.String(),
}),
])
export type SignUpAndAcceptRequestBody = Static<typeof SignUpAndAcceptRequestBody>

View File

@@ -0,0 +1,2 @@
export * from './enterprise-local-authn'
export * from './access-control-list'

View File

@@ -0,0 +1,135 @@
import { AiOverageState, isNil, PiecesFilterType, PlanName, PlatformPlanWithOnlyLimits, PlatformUsageMetric, TeamProjectsLimit } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export const PRICE_PER_EXTRA_ACTIVE_FLOWS = 5
export const AI_CREDITS_USAGE_THRESHOLD = 15000
export type ProjectPlanLimits = {
nickname?: string
locked?: boolean
pieces?: string[]
aiCredits?: number | null
piecesFilterType?: PiecesFilterType
}
export enum ApSubscriptionStatus {
ACTIVE = 'active',
CANCELED = 'canceled',
}
export const METRIC_TO_LIMIT_MAPPING = {
[PlatformUsageMetric.ACTIVE_FLOWS]: 'activeFlowsLimit',
} as const
export const METRIC_TO_USAGE_MAPPING = {
[PlatformUsageMetric.ACTIVE_FLOWS]: 'activeFlows',
} as const
export const SetAiCreditsOverageLimitParamsSchema = Type.Object({
limit: Type.Number({ minimum: 10 }),
})
export type SetAiCreditsOverageLimitParams = Static<typeof SetAiCreditsOverageLimitParamsSchema>
export const ToggleAiCreditsOverageEnabledParamsSchema = Type.Object({
state: Type.Enum(AiOverageState),
})
export type ToggleAiCreditsOverageEnabledParams = Static<typeof ToggleAiCreditsOverageEnabledParamsSchema>
export const UpdateActiveFlowsAddonParamsSchema = Type.Object({
newActiveFlowsLimit: Type.Number(),
})
export type UpdateActiveFlowsAddonParams = Static<typeof UpdateActiveFlowsAddonParamsSchema>
export const CreateCheckoutSessionParamsSchema = Type.Object({
newActiveFlowsLimit: Type.Number(),
})
export type CreateSubscriptionParams = Static<typeof CreateCheckoutSessionParamsSchema>
export enum PRICE_NAMES {
AI_CREDITS = 'ai-credit',
ACTIVE_FLOWS = 'active-flow',
}
export const PRICE_ID_MAP = {
[PRICE_NAMES.AI_CREDITS]: {
dev: 'price_1RnbNPQN93Aoq4f8GLiZbJFj',
prod: 'price_1Rnj5bKZ0dZRqLEKQx2gwL7s',
},
[PRICE_NAMES.ACTIVE_FLOWS]: {
dev: 'price_1SQbbYQN93Aoq4f8WK2JC4sf',
prod: 'price_1SQbcvKZ0dZRqLEKHV5UepRx',
},
}
export const STANDARD_CLOUD_PLAN: PlatformPlanWithOnlyLimits = {
plan: 'standard',
includedAiCredits: 200,
aiCreditsOverageLimit: undefined,
aiCreditsOverageState: AiOverageState.ALLOWED_BUT_OFF,
activeFlowsLimit: 10,
projectsLimit: 1,
agentsEnabled: true,
tablesEnabled: true,
todosEnabled: true,
mcpsEnabled: true,
embeddingEnabled: false,
globalConnectionsEnabled: false,
customRolesEnabled: false,
environmentsEnabled: false,
analyticsEnabled: true,
showPoweredBy: false,
auditLogEnabled: false,
managePiecesEnabled: false,
manageTemplatesEnabled: false,
customAppearanceEnabled: false,
teamProjectsLimit: TeamProjectsLimit.ONE,
projectRolesEnabled: false,
customDomainsEnabled: false,
apiKeysEnabled: false,
ssoEnabled: false,
}
export const OPEN_SOURCE_PLAN: PlatformPlanWithOnlyLimits = {
embeddingEnabled: false,
globalConnectionsEnabled: false,
customRolesEnabled: false,
mcpsEnabled: true,
tablesEnabled: true,
todosEnabled: true,
agentsEnabled: true,
includedAiCredits: 0,
aiCreditsOverageLimit: undefined,
aiCreditsOverageState: AiOverageState.NOT_ALLOWED,
environmentsEnabled: false,
analyticsEnabled: true,
showPoweredBy: false,
auditLogEnabled: false,
managePiecesEnabled: false,
manageTemplatesEnabled: false,
customAppearanceEnabled: false,
teamProjectsLimit: TeamProjectsLimit.NONE,
projectRolesEnabled: false,
customDomainsEnabled: false,
apiKeysEnabled: false,
ssoEnabled: false,
stripeCustomerId: undefined,
stripeSubscriptionId: undefined,
stripeSubscriptionStatus: undefined,
}
export const APPSUMO_PLAN = (planName: PlanName): PlatformPlanWithOnlyLimits => ({
...STANDARD_CLOUD_PLAN,
plan: planName,
activeFlowsLimit: undefined,
})
export const isCloudPlanButNotEnterprise = (plan?: string): boolean => {
if (isNil(plan)) {
return false
}
return plan === PlanName.STANDARD
}

View File

@@ -0,0 +1,32 @@
import { BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export enum CustomDomainStatus {
ACTIVE = 'ACTIVE',
PENDING = 'PENDING',
}
export const CustomDomain = Type.Object({
...BaseModelSchema,
domain: Type.String(),
platformId: Type.String(),
status: Type.Enum(CustomDomainStatus),
})
export type CustomDomain = Static<typeof CustomDomain>
export const AddDomainRequest = Type.Object({
domain: Type.String({
pattern: '^(?!.*\\.example\\.com$)(?!.*\\.example\\.net$).*',
}),
})
export type AddDomainRequest = Static<typeof AddDomainRequest>
export const ListCustomDomainsRequest = Type.Object({
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String()),
})
export type ListCustomDomainsRequest = Static<typeof ListCustomDomainsRequest>

View File

@@ -0,0 +1,83 @@
import { BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export enum GitBranchType {
PRODUCTION = 'PRODUCTION',
DEVELOPMENT = 'DEVELOPMENT',
}
export const GitRepo = Type.Object({
...BaseModelSchema,
remoteUrl: Type.String(),
branch: Type.String(),
branchType: Type.Enum(GitBranchType),
projectId: Type.String(),
sshPrivateKey: Type.String(),
slug: Type.String(),
})
export type GitRepo = Static<typeof GitRepo>
export const GitRepoWithoutSensitiveData = Type.Omit(GitRepo, ['sshPrivateKey'])
export type GitRepoWithoutSensitiveData = Static<typeof GitRepoWithoutSensitiveData>
export enum GitPushOperationType {
PUSH_FLOW = 'PUSH_FLOW',
DELETE_FLOW = 'DELETE_FLOW',
PUSH_TABLE = 'PUSH_TABLE',
DELETE_TABLE = 'DELETE_TABLE',
PUSH_EVERYTHING = 'PUSH_EVERYTHING',
}
export const PushFlowsGitRepoRequest = Type.Object({
type: Type.Union([Type.Literal(GitPushOperationType.PUSH_FLOW), Type.Literal(GitPushOperationType.DELETE_FLOW)]),
commitMessage: Type.String({
minLength: 1,
}),
externalFlowIds: Type.Array(Type.String()),
})
export type PushFlowsGitRepoRequest = Static<typeof PushFlowsGitRepoRequest>
export const PushTablesGitRepoRequest = Type.Object({
type: Type.Union([Type.Literal(GitPushOperationType.PUSH_TABLE), Type.Literal(GitPushOperationType.DELETE_TABLE)]),
commitMessage: Type.String({
minLength: 1,
}),
externalTableIds: Type.Array(Type.String()),
})
export type PushTablesGitRepoRequest = Static<typeof PushTablesGitRepoRequest>
export const PushEverythingGitRepoRequest = Type.Object({
type: Type.Literal(GitPushOperationType.PUSH_EVERYTHING),
commitMessage: Type.String({
minLength: 1,
}),
})
export type PushEverythingGitRepoRequest = Static<typeof PushEverythingGitRepoRequest>
export const PushGitRepoRequest = Type.Union([PushFlowsGitRepoRequest, PushTablesGitRepoRequest, PushEverythingGitRepoRequest])
export type PushGitRepoRequest = Static<typeof PushGitRepoRequest>
export const ConfigureRepoRequest = Type.Object({
projectId: Type.String({
minLength: 1,
}),
remoteUrl: Type.String({
pattern: '^git@',
}),
branch: Type.String({
minLength: 1,
}),
branchType: Type.Enum(GitBranchType),
sshPrivateKey: Type.String({
minLength: 1,
}),
slug: Type.String({
minLength: 1,
}),
})
export type ConfigureRepoRequest = Static<typeof ConfigureRepoRequest>

View File

@@ -0,0 +1 @@
export * from './managed-authn-requests'

View File

@@ -0,0 +1,8 @@
import { Static, Type } from '@sinclair/typebox'
export const ManagedAuthnRequestBody = Type.Object({
//if you change this you need to update the embed-sdk I can't import it there because it can't have dependencies
externalAccessToken: Type.String(),
})
export type ManagedAuthnRequestBody = Static<typeof ManagedAuthnRequestBody>

View File

@@ -0,0 +1 @@
export * from './oauth-app'

View File

@@ -0,0 +1,26 @@
import { BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export const OAuthApp = Type.Object({
...BaseModelSchema,
pieceName: Type.String(),
platformId: Type.String(),
clientId: Type.String(),
})
export type OAuthApp = Static<typeof OAuthApp>
export const UpsertOAuth2AppRequest = Type.Object({
pieceName: Type.String(),
clientId: Type.String(),
clientSecret: Type.String(),
})
export type UpsertOAuth2AppRequest = Static<typeof UpsertOAuth2AppRequest>
export const ListOAuth2AppRequest = Type.Object({
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String()),
})
export type ListOAuth2AppRequest = Static<typeof ListOAuth2AppRequest>

View File

@@ -0,0 +1,3 @@
export * from './otp-model'
export * from './otp-requests'
export * from './otp-type'

View File

@@ -0,0 +1,20 @@
import { ApId, BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
import { OtpType } from './otp-type'
export type OtpId = ApId
export enum OtpState {
PENDING = 'PENDING',
CONFIRMED = 'CONFIRMED',
}
export const OtpModel = Type.Object({
...BaseModelSchema,
type: Type.Enum(OtpType),
identityId: ApId,
value: Type.String(),
state: Type.Enum(OtpState),
})
export type OtpModel = Static<typeof OtpModel>

View File

@@ -0,0 +1,11 @@
import { Static, Type } from '@sinclair/typebox'
import { OtpType } from './otp-type'
export const CreateOtpRequestBody = Type.Object({
email: Type.String(),
type: Type.Enum(OtpType),
})
export type CreateOtpRequestBody = Static<typeof CreateOtpRequestBody>

View File

@@ -0,0 +1,4 @@
export enum OtpType {
EMAIL_VERIFICATION = 'EMAIL_VERIFICATION',
PASSWORD_RESET = 'PASSWORD_RESET',
}

View File

@@ -0,0 +1,38 @@
import { Static, Type } from '@sinclair/typebox'
import { AppCredentialType } from './app-credentials'
export const ListAppCredentialsRequest = Type.Object({
projectId: Type.String(),
appName: Type.Optional(Type.String()),
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String({})),
})
export type ListAppCredentialsRequest = Static<typeof ListAppCredentialsRequest>
export const UpsertApiKeyCredentialRequest = Type.Object({
id: Type.Optional(Type.String()),
appName: Type.String(),
settings: Type.Object({
type: Type.Literal(AppCredentialType.API_KEY),
}),
})
export const UpsertOAuth2CredentialRequest = Type.Object({
id: Type.Optional(Type.String()),
appName: Type.String(),
settings: Type.Object({
type: Type.Literal(AppCredentialType.OAUTH2),
authUrl: Type.String({}),
scope: Type.String(),
tokenUrl: Type.String({}),
clientId: Type.String({}),
clientSecret: Type.String({}),
}),
})
export const UpsertAppCredentialRequest = Type.Union([UpsertOAuth2CredentialRequest, UpsertApiKeyCredentialRequest])
export type UpsertAppCredentialRequest = Static<typeof UpsertAppCredentialRequest>

View File

@@ -0,0 +1,29 @@
import { BaseModel, OAuth2GrantType, ProjectId } from '@activepieces/shared'
export type AppCredentialId = string
export type AppOAuth2Settings = {
type: AppCredentialType.OAUTH2
authUrl: string
tokenUrl: string
grantType: OAuth2GrantType
clientId: string
clientSecret?: string
scope: string
}
export type AppApiKeySettings = {
type: AppCredentialType.API_KEY
}
export type AppCredential = {
appName: string
projectId: ProjectId
settings: AppOAuth2Settings | AppApiKeySettings
} & BaseModel<AppCredentialId>
export enum AppCredentialType {
OAUTH2 = 'OAUTH2',
API_KEY = 'API_KEY',
}

View File

@@ -0,0 +1,2 @@
export * from './app-credentials'
export * from './app-credentials-requests'

View File

@@ -0,0 +1,18 @@
import { BaseModel, ProjectId } from '@activepieces/shared'
export type ConnectionKeyId = string
export type ConnectionKey = {
projectId: ProjectId
settings: SigningKeyConnection
} & BaseModel<ConnectionKeyId>
export type SigningKeyConnection = {
type: ConnectionKeyType.SIGNING_KEY
publicKey: string
privateKey?: string
}
export enum ConnectionKeyType {
SIGNING_KEY = 'SIGNING_KEY',
}

View File

@@ -0,0 +1,49 @@
import { Static, Type } from '@sinclair/typebox'
import { ConnectionKeyType } from './connection-key'
export const GetOrDeleteConnectionFromTokenRequest = Type.Object({
projectId: Type.String(),
token: Type.String(),
appName: Type.String(),
})
export type GetOrDeleteConnectionFromTokenRequest = Static<typeof GetOrDeleteConnectionFromTokenRequest>
export const ListConnectionKeysRequest = Type.Object({
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String({})),
})
export type ListConnectionKeysRequest = Static<typeof ListConnectionKeysRequest>
export const UpsertApiKeyConnectionFromToken = Type.Object({
appCredentialId: Type.String(),
apiKey: Type.String(),
token: Type.String(),
})
export type UpsertApiKeyConnectionFromToken = Static<typeof UpsertApiKeyConnectionFromToken>
export const UpsertOAuth2ConnectionFromToken = Type.Object({
appCredentialId: Type.String(),
props: Type.Record(Type.String(), Type.Any()),
token: Type.String(),
code: Type.String(),
redirectUrl: Type.String(),
})
export type UpsertOAuth2ConnectionFromToken = Static<typeof UpsertOAuth2ConnectionFromToken>
export const UpsertConnectionFromToken = Type.Union([UpsertApiKeyConnectionFromToken, UpsertOAuth2ConnectionFromToken])
export type UpsertConnectionFromToken = Static<typeof UpsertConnectionFromToken>
export const UpsertSigningKeyConnection = Type.Object({
settings: Type.Object({
type: Type.Literal(ConnectionKeyType.SIGNING_KEY),
}),
})
export type UpsertSigningKeyConnection = Static<typeof UpsertSigningKeyConnection>

View File

@@ -0,0 +1,2 @@
export * from './connection-key'
export * from './connection-requests'

View File

@@ -0,0 +1,28 @@
import { Static, Type } from '@sinclair/typebox'
export const AcceptInvitationRequest = Type.Object({
token: Type.String(),
})
export type AcceptInvitationRequest = Static<typeof AcceptInvitationRequest>
export const ListProjectMembersRequestQuery = Type.Object({
projectId: Type.String(),
projectRoleId: Type.Optional(Type.String()),
cursor: Type.Optional(Type.String()),
limit: Type.Optional(Type.Number()),
})
export type ListProjectMembersRequestQuery = Static<typeof ListProjectMembersRequestQuery>
export const AcceptProjectResponse = Type.Object({
registered: Type.Boolean(),
})
export type AcceptProjectResponse = Static<typeof AcceptProjectResponse>
export const UpdateProjectMemberRoleRequestBody = Type.Object({
role: Type.String(),
})
export type UpdateProjectMemberRoleRequestBody = Static<typeof UpdateProjectMemberRoleRequestBody>

View File

@@ -0,0 +1,24 @@
import { ApId, BaseModelSchema, ProjectMetaData, ProjectRole, UserWithMetaInformation } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export type ProjectMemberId = string
export const ProjectMember = Type.Object({
...BaseModelSchema,
platformId: ApId,
userId: ApId,
projectId: Type.String(),
projectRoleId: ApId,
}, {
description: 'Project member is which user is assigned to a project.',
})
export type ProjectMember = Static<typeof ProjectMember>
export const ProjectMemberWithUser = Type.Composite([ProjectMember, Type.Object({
user: UserWithMetaInformation,
projectRole: ProjectRole,
project: ProjectMetaData,
})])
export type ProjectMemberWithUser = Static<typeof ProjectMemberWithUser>

View File

@@ -0,0 +1,39 @@
import { Metadata, Nullable, PiecesFilterType, ProjectIcon, ProjectType, SAFE_STRING_PATTERN } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export const UpdateProjectPlatformRequest = Type.Object({
releasesEnabled: Type.Optional(Type.Boolean()),
displayName: Type.Optional(Type.String({
pattern: SAFE_STRING_PATTERN,
})),
externalId: Type.Optional(Type.String()),
metadata: Type.Optional(Metadata),
icon: Type.Optional(ProjectIcon),
plan: Type.Optional(Type.Object({
pieces: Type.Optional(Type.Array(Type.String({}))),
piecesFilterType: Type.Optional(Type.Enum(PiecesFilterType)),
})),
})
export type UpdateProjectPlatformRequest = Static<typeof UpdateProjectPlatformRequest>
export const CreatePlatformProjectRequest = Type.Object({
displayName: Type.String({
pattern: SAFE_STRING_PATTERN,
}),
externalId: Nullable(Type.String()),
metadata: Nullable(Metadata),
maxConcurrentJobs: Nullable(Type.Number()),
})
export type CreatePlatformProjectRequest = Static<typeof CreatePlatformProjectRequest>
export const ListProjectRequestForPlatformQueryParams = Type.Object({
externalId: Type.Optional(Type.String()),
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
displayName: Type.Optional(Type.String()),
types: Type.Optional(Type.Array(Type.Enum(ProjectType))),
})
export type ListProjectRequestForPlatformQueryParams = Static<typeof ListProjectRequestForPlatformQueryParams>

View File

@@ -0,0 +1,3 @@
export * from './signing-key-model'
export * from './signing-key-response'
export * from './signing-key.request'

View File

@@ -0,0 +1,19 @@
import { ApId, BaseModelSchema } from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
export enum KeyAlgorithm {
RSA = 'RSA',
}
export type SigningKeyId = ApId
export const SigningKey = Type.Object({
...BaseModelSchema,
platformId: ApId,
publicKey: Type.String(),
displayName: Type.String(),
/* algorithm used to generate this key pair */
algorithm: Type.Enum(KeyAlgorithm),
})
export type SigningKey = Static<typeof SigningKey>

View File

@@ -0,0 +1,5 @@
import { SigningKey } from './signing-key-model'
export type AddSigningKeyResponse = SigningKey & {
privateKey: string
}

View File

@@ -0,0 +1,7 @@
import { Static, Type } from '@sinclair/typebox'
export const AddSigningKeyRequestBody = Type.Object({
displayName: Type.String(),
})
export type AddSigningKeyRequestBody = Static<typeof AddSigningKeyRequestBody>

View File

@@ -0,0 +1,9 @@
import { Static, Type } from '@sinclair/typebox'
export const GetFlowTemplateRequestQuery = Type.Object({
versionId: Type.Optional(Type.String()),
})
export type GetFlowTemplateRequestQuery = Static<typeof GetFlowTemplateRequestQuery>

View File

@@ -0,0 +1 @@
export * from './flow-template.request'

View File

@@ -0,0 +1 @@
export * from './flow-template'

View File

@@ -0,0 +1,19 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View File

@@ -0,0 +1,17 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"rules": {
"@typescript-eslint/adjacent-overload-signatures": "off",
"@typescript-eslint/no-non-null-assertion": "off"
},
"extends": [
"plugin:prettier/recommended"
],
"plugins": ["prettier"]
}
]
}

View File

@@ -0,0 +1,18 @@
{
"extends": ["../../../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,9 @@
# ee-embed-sdk
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx bundle ee-embed-sdk` to build the library.
Check project.json "output" property to see the generated file location and its name.

View File

@@ -0,0 +1,7 @@
{
"name": "ee-embed-sdk",
"version": "0.8.1",
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
}

View File

@@ -0,0 +1,46 @@
{
"name": "ee-embed-sdk",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/ee/ui/embed-sdk/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/ee/ui/embed-sdk",
"main": "packages/ee/ui/embed-sdk/src/index.ts",
"tsConfig": "packages/ee/ui/embed-sdk/tsconfig.lib.json",
"assets": []
}
},
"bundle": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"target": "web",
"compiler": "tsc",
"outputFileName": "bundled.js",
"outputPath": "dist/packages/ee/ui/embed-sdk",
"main": "packages/ee/ui/embed-sdk/src/index.ts",
"tsConfig": "packages/ee/ui/embed-sdk/tsconfig.lib.json",
"assets": [],
"webpackConfig": "packages/ee/ui/embed-sdk/webpack.config.js",
"generatePackageJson": true,
"babelUpwardRootMode": true
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}

View File

@@ -0,0 +1,618 @@
//Client ==> Activepieces
//Vendor ==> Customers using our embed sdk
export enum ActivepiecesClientEventName {
CLIENT_INIT = 'CLIENT_INIT',
CLIENT_ROUTE_CHANGED = 'CLIENT_ROUTE_CHANGED',
CLIENT_NEW_CONNECTION_DIALOG_CLOSED = 'CLIENT_NEW_CONNECTION_DIALOG_CLOSED',
CLIENT_SHOW_CONNECTION_IFRAME = 'CLIENT_SHOW_CONNECTION_IFRAME',
CLIENT_CONNECTION_NAME_IS_INVALID = 'CLIENT_CONNECTION_NAME_IS_INVALID',
CLIENT_AUTHENTICATION_SUCCESS = 'CLIENT_AUTHENTICATION_SUCCESS',
CLIENT_AUTHENTICATION_FAILED = 'CLIENT_AUTHENTICATION_FAILED',
CLIENT_CONFIGURATION_FINISHED = 'CLIENT_CONFIGURATION_FINISHED',
CLIENT_CONNECTION_PIECE_NOT_FOUND = 'CLIENT_CONNECTION_PIECE_NOT_FOUND',
CLIENT_BUILDER_HOME_BUTTON_CLICKED = 'CLIENT_BUILDER_HOME_BUTTON_CLICKED',
}
export interface ActivepiecesClientInit {
type: ActivepiecesClientEventName.CLIENT_INIT;
data: Record<string, never>;
}
export interface ActivepiecesClientAuthenticationSuccess {
type: ActivepiecesClientEventName.CLIENT_AUTHENTICATION_SUCCESS;
data: Record<string, never>;
}
export interface ActivepiecesClientAuthenticationFailed {
type: ActivepiecesClientEventName.CLIENT_AUTHENTICATION_FAILED;
data: unknown;
}
// Added this event so in the future if we add another step between authentication and configuration finished, we can use this event to notify the parent
export interface ActivepiecesClientConfigurationFinished {
type: ActivepiecesClientEventName.CLIENT_CONFIGURATION_FINISHED;
data: Record<string, never>;
}
export interface ActivepiecesClientShowConnectionIframe {
type: ActivepiecesClientEventName.CLIENT_SHOW_CONNECTION_IFRAME;
data: Record<string, never>;
}
export interface ActivepiecesClientConnectionNameIsInvalid {
type: ActivepiecesClientEventName.CLIENT_CONNECTION_NAME_IS_INVALID;
data: {
error: string;
};
}
export interface ActivepiecesClientConnectionPieceNotFound {
type: ActivepiecesClientEventName.CLIENT_CONNECTION_PIECE_NOT_FOUND;
data: {
error: string
};
}
export interface ActivepiecesClientRouteChanged {
type: ActivepiecesClientEventName.CLIENT_ROUTE_CHANGED;
data: {
route: string;
};
}
export interface ActivepiecesNewConnectionDialogClosed {
type: ActivepiecesClientEventName.CLIENT_NEW_CONNECTION_DIALOG_CLOSED;
data: { connection?: { id: string; name: string } };
}
export interface ActivepiecesBuilderHomeButtonClicked {
type: ActivepiecesClientEventName.CLIENT_BUILDER_HOME_BUTTON_CLICKED;
data: {
route: string;
};
}
type IframeWithWindow = HTMLIFrameElement & { contentWindow: Window };
export const NEW_CONNECTION_QUERY_PARAMS = {
name: 'pieceName',
connectionName: 'connectionName',
randomId: 'randomId'
};
export type ActivepiecesClientEvent =
| ActivepiecesClientInit
| ActivepiecesClientRouteChanged;
export enum ActivepiecesVendorEventName {
VENDOR_INIT = 'VENDOR_INIT',
VENDOR_ROUTE_CHANGED = 'VENDOR_ROUTE_CHANGED',
}
export interface ActivepiecesVendorRouteChanged {
type: ActivepiecesVendorEventName.VENDOR_ROUTE_CHANGED;
data: {
vendorRoute: string;
};
}
export interface ActivepiecesVendorInit {
type: ActivepiecesVendorEventName.VENDOR_INIT;
data: {
hideSidebar: boolean;
hideFlowNameInBuilder?: boolean;
disableNavigationInBuilder: boolean | 'keep_home_button_only';
hideFolders?: boolean;
sdkVersion?: string;
jwtToken: string;
initialRoute?: string
fontUrl?: string;
fontFamily?: string;
hideExportAndImportFlow?: boolean;
hideDuplicateFlow?: boolean;
homeButtonIcon?: 'back' | 'logo';
emitHomeButtonClickedEvent?: boolean;
locale?: string;
mode?: 'light' | 'dark';
hideFlowsPageNavbar?: boolean;
hidePageHeader?: boolean;
};
}
type newWindowFeatures = {
height?: number,
width?: number,
top?: number,
left?: number,
}
type EmbeddingParam = {
containerId?: string;
styling?: {
fontUrl?: string;
fontFamily?: string;
mode?: 'light' | 'dark';
};
locale?:string;
builder?: {
disableNavigation?: boolean;
hideFlowName?: boolean;
homeButtonIcon: 'back' | 'logo';
homeButtonClickedHandler?: (data: {
route: string;
}) => void;
};
dashboard?: {
hideSidebar?: boolean;
hideFlowsPageNavbar?: boolean;
hidePageHeader?: boolean;
};
hideExportAndImportFlow?: boolean;
hideDuplicateFlow?: boolean;
hideFolders?: boolean;
navigation?: {
handler?: (data: { route: string }) => void;
}
}
type ConfigureParams = {
instanceUrl: string;
jwtToken: string;
prefix?: string;
embedding?: EmbeddingParam;
}
type RequestMethod = Required<Parameters<typeof fetch>>[1]['method'];
class ActivepiecesEmbedded {
readonly _sdkVersion = "0.8.1";
//used for Automatically Sync URL feature i.e /org/1234
_prefix = '/';
_instanceUrl = '';
//this is used to authenticate embedding for the first time
_jwtToken = '';
_resolveNewConnectionDialogClosed?: (result: ActivepiecesNewConnectionDialogClosed['data']) => void;
_dashboardAndBuilderIframeWindow?: Window;
_rejectNewConnectionDialogClosed?: (error: unknown) => void;
_handleVendorNavigation?: (data: { route: string }) => void;
_handleClientNavigation?: (data: { route: string }) => void;
_parentOrigin = window.location.origin;
readonly _MAX_CONTAINER_CHECK_COUNT = 100;
readonly _HUNDRED_MILLISECONDS = 100;
_embeddingAuth?: {
//this is used to do authentication with the backend
userJwtToken:string,
platformId:string,
projectId:string
};
_embeddingState?: EmbeddingParam;
configure({
jwtToken,
instanceUrl,
embedding,
prefix,
}: ConfigureParams) {
this._instanceUrl = this._removeTrailingSlashes(instanceUrl);
this._jwtToken = jwtToken;
this._prefix = this._removeTrailingSlashes(this._prependForwardSlashToRoute(prefix ?? '/'));
this._embeddingState = embedding;
if (embedding?.containerId) {
return this._initializeBuilderAndDashboardIframe({
containerSelector: `#${embedding.containerId}`
});
}
return new Promise((resolve) => { resolve({ status: "success" }) });
}
private _initializeBuilderAndDashboardIframe = ({
containerSelector
}: {
containerSelector: string
}) => {
return new Promise((resolve, reject) => {
this._addGracePeriodBeforeMethod({
condition: () => {
return !!document.querySelector(containerSelector);
},
method: () => {
const iframeContainer = document.querySelector(containerSelector);
if (iframeContainer) {
const iframeWindow = this.connectToEmbed({
iframeContainer,
callbackAfterConfigurationFinished: () => {
resolve({ status: "success" });
},
initialRoute: '/'
}).contentWindow;
this._dashboardAndBuilderIframeWindow = iframeWindow;
this._checkForClientRouteChanges(iframeWindow);
this._checkForBuilderHomeButtonClicked(iframeWindow);
}
else {
reject({
status: "error",
error: {
message: 'container not found',
},
});
}
},
errorMessage: 'container not found',
});
});
};
private _setupInitialMessageHandler(targetWindow: Window, initialRoute: string, callbackAfterConfigurationFinished?: () => void) {
const initialMessageHandler = (event: MessageEvent<ActivepiecesClientEvent>) => {
if (event.source === targetWindow && event.origin === new URL(this._instanceUrl).origin) {
switch (event.data.type) {
case ActivepiecesClientEventName.CLIENT_INIT: {
const apEvent: ActivepiecesVendorInit = {
type: ActivepiecesVendorEventName.VENDOR_INIT,
data: {
hideSidebar: this._embeddingState?.dashboard?.hideSidebar ?? false,
hideFlowsPageNavbar: this._embeddingState?.dashboard?.hideFlowsPageNavbar ?? false,
disableNavigationInBuilder: this._embeddingState?.builder?.disableNavigation ?? false,
hideFolders: this._embeddingState?.hideFolders ?? false,
hideFlowNameInBuilder: this._embeddingState?.builder?.hideFlowName ?? false,
jwtToken: this._jwtToken,
initialRoute,
fontUrl: this._embeddingState?.styling?.fontUrl,
fontFamily: this._embeddingState?.styling?.fontFamily,
hideExportAndImportFlow: this._embeddingState?.hideExportAndImportFlow ?? false,
emitHomeButtonClickedEvent: this._embeddingState?.builder?.homeButtonClickedHandler !== undefined,
locale: this._embeddingState?.locale ?? 'en',
sdkVersion: this._sdkVersion,
homeButtonIcon: this._embeddingState?.builder?.homeButtonIcon ?? 'logo',
hideDuplicateFlow: this._embeddingState?.hideDuplicateFlow ?? false,
mode: this._embeddingState?.styling?.mode,
hidePageHeader: this._embeddingState?.dashboard?.hidePageHeader ?? false,
},
};
targetWindow.postMessage(apEvent, '*');
this._createAuthenticationSuccessListener(targetWindow);
this._createAuthenticationFailedListener(targetWindow);
this._createConfigurationFinishedListener(targetWindow, callbackAfterConfigurationFinished);
window.removeEventListener('message', initialMessageHandler);
break;
}
}
}
};
window.addEventListener('message', initialMessageHandler);
}
private connectToEmbed({ iframeContainer, initialRoute, callbackAfterConfigurationFinished }: {
iframeContainer: Element,
initialRoute: string,
callbackAfterConfigurationFinished?: () => void
}): IframeWithWindow {
const iframe = this._createIframe({ src: `${this._instanceUrl}/embed?currentDate=${Date.now()}` });
iframeContainer.appendChild(iframe);
if (!this._doesFrameHaveWindow(iframe)) {
this._errorCreator('iframe window not accessible');
}
const iframeWindow = iframe.contentWindow;
this._setupInitialMessageHandler(iframeWindow, initialRoute, callbackAfterConfigurationFinished);
return iframe;
}
private _createConfigurationFinishedListener = (targetWindow: Window, callbackAfterConfigurationFinished?: () => void) => {
const configurationFinishedHandler = (event: MessageEvent<ActivepiecesClientConfigurationFinished>) => {
if (event.data.type === ActivepiecesClientEventName.CLIENT_CONFIGURATION_FINISHED && event.source === targetWindow) {
this._logger().log('Configuration finished')
if (callbackAfterConfigurationFinished) {
callbackAfterConfigurationFinished();
}
}
}
window.addEventListener('message', configurationFinishedHandler);
}
private _createAuthenticationFailedListener = (targetWindow: Window) => {
const authenticationFailedHandler = (event: MessageEvent<ActivepiecesClientAuthenticationFailed>) => {
if (event.data.type === ActivepiecesClientEventName.CLIENT_AUTHENTICATION_FAILED && event.source === targetWindow) {
this._errorCreator('Authentication failed',event.data.data);
}
}
window.addEventListener('message', authenticationFailedHandler);
}
private _createAuthenticationSuccessListener = (targetWindow: Window) => {
const authenticationSuccessHandler = (event: MessageEvent<ActivepiecesClientAuthenticationSuccess>) => {
if (event.data.type === ActivepiecesClientEventName.CLIENT_AUTHENTICATION_SUCCESS && event.source === targetWindow) {
this._logger().log('Authentication success')
window.removeEventListener('message', authenticationSuccessHandler);
}
}
window.addEventListener('message', authenticationSuccessHandler);
}
private _createIframe({ src }: { src: string }) {
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
return iframe;
}
private _getNewWindowFeatures(requestedFeats:newWindowFeatures) {
const windowFeats:newWindowFeatures = {
height: 700,
width: 700,
top: 0,
left: 0,
}
Object.keys(windowFeats).forEach((key) => {
if(typeof requestedFeats === 'object' && requestedFeats[key as keyof newWindowFeatures]){
windowFeats[key as keyof newWindowFeatures ] = requestedFeats[key as keyof typeof requestedFeats]
}
})
return `width=${windowFeats.width},height=${windowFeats.height},top=${windowFeats.top},left=${windowFeats.left}`
}
private _addConnectionIframe({pieceName, connectionName}:{pieceName:string, connectionName?:string}) {
const connectionsIframe = this.connectToEmbed({
iframeContainer: document.body,
initialRoute: `/embed/connections?${NEW_CONNECTION_QUERY_PARAMS.name}=${pieceName}&randomId=${Date.now()}&${NEW_CONNECTION_QUERY_PARAMS.connectionName}=${connectionName || ''}`
});
connectionsIframe.style.cssText = ['display:none', 'position:fixed', 'top:0', 'left:0', 'width:100%', 'height:100%', 'border:none'].join(';');
return connectionsIframe;
}
private _openNewWindowForConnections({pieceName, connectionName,newWindow}:{pieceName:string, connectionName?:string, newWindow:newWindowFeatures}) {
const popup = window.open(`${this._instanceUrl}/embed`, '_blank', this._getNewWindowFeatures(newWindow));
if (!popup) {
this._errorCreator('Failed to open popup window');
}
this._setupInitialMessageHandler(popup, `/embed/connections?${NEW_CONNECTION_QUERY_PARAMS.name}=${pieceName}&randomId=${Date.now()}&${NEW_CONNECTION_QUERY_PARAMS.connectionName}=${connectionName || ''}`);
return popup;
}
async connect({ pieceName, connectionName, newWindow }: {
pieceName: string,
connectionName?: string,
newWindow?:{
height?: number,
width?: number,
top?: number,
left?: number,
}
}) {
this._cleanConnectionIframe();
return this._addGracePeriodBeforeMethod({
condition: () => {
return !!document.body;
},
method: async () => {
const target = newWindow? this._openNewWindowForConnections({pieceName, connectionName,newWindow}) : this._addConnectionIframe({pieceName, connectionName});
//don't check for window because (instanceof Window) is false for popups
if(!(target instanceof HTMLIFrameElement)) {
const checkClosed = setInterval(() => {
if (target.closed) {
clearInterval(checkClosed);
if(this._resolveNewConnectionDialogClosed) {
this._resolveNewConnectionDialogClosed({connection:undefined})
}
}
}, 500);
}
return new Promise<ActivepiecesNewConnectionDialogClosed['data']>((resolve, reject) => {
this._resolveNewConnectionDialogClosed = resolve;
this._rejectNewConnectionDialogClosed = reject;
this._setConnectionIframeEventsListener(target);
});
},
errorMessage: 'unable to add connection embedding'
});
}
navigate({ route }: { route: string }) {
if (!this._dashboardAndBuilderIframeWindow) {
this._logger().error('dashboard iframe not found');
return;
}
const event: ActivepiecesVendorRouteChanged = {
type: ActivepiecesVendorEventName.VENDOR_ROUTE_CHANGED,
data: {
vendorRoute: this._prependForwardSlashToRoute(route),
},
};
this._dashboardAndBuilderIframeWindow.postMessage(event, '*');
}
private _prependForwardSlashToRoute(route: string) {
return route.startsWith('/') ? route : `/${route}`;
}
private _checkForClientRouteChanges = (source: Window) => {
window.addEventListener(
'message',
(event: MessageEvent<ActivepiecesClientRouteChanged>) => {
if (
event.data.type ===
ActivepiecesClientEventName.CLIENT_ROUTE_CHANGED &&
event.source === source &&
this._embeddingState?.navigation?.handler
) {
const routeWithPrefix = this._prefix + this._prependForwardSlashToRoute(event.data.data.route);
this._embeddingState.navigation.handler({ route: routeWithPrefix });
return;
}
}
);
};
private _checkForBuilderHomeButtonClicked = (source: Window) => {
window.addEventListener('message', (event: MessageEvent<ActivepiecesBuilderHomeButtonClicked>) => {
if (event.data.type === ActivepiecesClientEventName.CLIENT_BUILDER_HOME_BUTTON_CLICKED && event.source === source) {
this._embeddingState?.builder?.homeButtonClickedHandler?.(event.data.data);
}
});
}
private _extractRouteAfterPrefix(vendorUrl: string, parentOriginWithPrefix: string) {
return vendorUrl.split(parentOriginWithPrefix)[1];
}
//used for Automatically Sync URL feature
extractActivepiecesRouteFromUrl({ vendorUrl }: { vendorUrl: string }) {
return this._extractRouteAfterPrefix(vendorUrl, this._removeTrailingSlashes(this._parentOrigin) + this._prefix);
}
private _doesFrameHaveWindow(
frame: HTMLIFrameElement
): frame is IframeWithWindow {
return frame.contentWindow !== null;
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
private _cleanConnectionIframe = () => { };
private _setConnectionIframeEventsListener(target: Window | HTMLIFrameElement ) {
const connectionRelatedMessageHandler = (event: MessageEvent<ActivepiecesNewConnectionDialogClosed | ActivepiecesClientConnectionNameIsInvalid | ActivepiecesClientShowConnectionIframe | ActivepiecesClientConnectionPieceNotFound>) => {
if (event.data.type) {
switch (event.data.type) {
case ActivepiecesClientEventName.CLIENT_NEW_CONNECTION_DIALOG_CLOSED: {
if (this._resolveNewConnectionDialogClosed) {
this._resolveNewConnectionDialogClosed(event.data.data);
}
this._removeEmbedding(target);
window.removeEventListener('message', connectionRelatedMessageHandler);
break;
}
case ActivepiecesClientEventName.CLIENT_CONNECTION_NAME_IS_INVALID:
case ActivepiecesClientEventName.CLIENT_CONNECTION_PIECE_NOT_FOUND: {
this._removeEmbedding(target);
if (this._rejectNewConnectionDialogClosed) {
this._rejectNewConnectionDialogClosed(event.data.data);
}
else {
this._errorCreator(event.data.data.error);
}
window.removeEventListener('message', connectionRelatedMessageHandler);
break;
}
case ActivepiecesClientEventName.CLIENT_SHOW_CONNECTION_IFRAME: {
if (target instanceof HTMLIFrameElement) {
target.style.display = 'block';
}
break;
}
}
}
}
window.addEventListener(
'message',
connectionRelatedMessageHandler
);
this._cleanConnectionIframe = () => {
window.removeEventListener('message', connectionRelatedMessageHandler);
this._resolveNewConnectionDialogClosed = undefined;
this._rejectNewConnectionDialogClosed = undefined;
this._removeEmbedding(target);
}
}
private _removeTrailingSlashes(str: string) {
return str.endsWith('/') ? str.slice(0, -1) : str;
}
private _removeStartingSlashes(str: string) {
return str.startsWith('/') ? str.slice(1) : str;
}
/**Adds a grace period before executing the method depending on the condition */
private _addGracePeriodBeforeMethod({
method,
condition,
errorMessage,
}: {
method: () => Promise<any> | void;
condition: () => boolean;
/**Error message to show when grace period passes */
errorMessage: string;
}) {
return new Promise((resolve, reject) => {
let checkCounter = 0;
if (condition()) {
resolve(method());
return;
}
const checker = setInterval(() => {
if (checkCounter >= this._MAX_CONTAINER_CHECK_COUNT) {
this._logger().error(errorMessage);
reject(errorMessage);
return;
}
checkCounter++;
if (condition()) {
clearInterval(checker);
resolve(method());
}
}, this._HUNDRED_MILLISECONDS);
},);
}
private _errorCreator(message: string,...args:any[]): never {
this._logger().error(message,...args)
throw new Error(`Activepieces: ${message}`,);
}
private _removeEmbedding(target:HTMLIFrameElement | Window) {
if (target) {
if (target instanceof HTMLIFrameElement) {
target.remove();
} else {
target.close();
}
}
else {
this._logger().warn(`couldn't remove embedding`)
}
}
private _logger() {
return{
log: (message: string, ...args: any[]) => {
console.log(`Activepieces: ${message}`, ...args)
},
error: (message: string, ...args: any[]) => {
console.error(`Activepieces: ${message}`, ...args)
},
warn: (message: string, ...args: any[]) => {
console.warn(`Activepieces: ${message}`, ...args)
}
}
}
private async fetchEmbeddingAuth(params:{jwtToken:string} | undefined) {
if(this._embeddingAuth) {
return this._embeddingAuth;
}
const jwtToken = params?.jwtToken?? this._jwtToken;
if(!jwtToken) {
this._errorCreator('jwt token not found');
}
const response = await this.request({path: '/managed-authn/external-token', method: 'POST', body: {
externalAccessToken: jwtToken,
}}, false)
this._embeddingAuth = {
userJwtToken: response.token,
platformId: response.platformId,
projectId: response.projectId,
}
return this._embeddingAuth;
}
async request({path, method, body, queryParams}:{path:string, method: RequestMethod, body?:Record<string, unknown>, queryParams?:Record<string, string>}, useJwtToken = true) {
const headers:Record<string, string> = {
}
if(body) {
headers['Content-Type'] = 'application/json'
}
if(useJwtToken) {
const embeddingAuth = await this.fetchEmbeddingAuth({jwtToken: this._jwtToken});
headers['Authorization'] = `Bearer ${embeddingAuth.userJwtToken}`
}
const queryParamsString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : '';
return fetch(`${this._removeTrailingSlashes(this._instanceUrl)}/api/v1/${this._removeStartingSlashes(path)}${queryParamsString}`, {
method,
body: body ? JSON.stringify(body) : undefined,
headers,
}).then(res => res.json())
}
}
(window as any).activepieces = new ActivepiecesEmbedded();
(window as any).ActivepiecesEmbedded = ActivepiecesEmbedded;

View File

@@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "amd",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View File

@@ -0,0 +1,5 @@
const { composePlugins, withNx } = require('@nx/webpack');
module.exports = composePlugins(withNx(), (config) => {
return config;
});