diff --git a/bun.lockb b/bun.lockb index a1b65bee..5bc6c495 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 682227ac..80bfb648 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@dialectlabs/blinks", - "version": "0.9.4", + "name": "@dialectlabs/blinks-monorepo", + "version": "1.0.0", "license": "Apache-2.0", "private": false, "sideEffects": true, @@ -10,45 +10,17 @@ "url": "https://github.com/dialectlabs/blinks" }, "scripts": { - "build": "tsup-node", - "dev": "tsup-node --watch" + "build:blinks": "bun --filter \"@dialectlabs/blinks\" build", + "build:blinks-core": "bun --filter \"@dialectlabs/blinks-core\" build", + "build": "bun build:blinks-core && bun build:blinks", + "dev": "bun --filter \"*\" dev", + "dev:blinks": "bun --filter \"@dialectlabs/blinks\" dev", + "dev:blinks-core": "bun --filter \"@dialectlabs/blinks-core\" dev" }, - "main": "dist/index.cjs", - "module": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - "./ext/twitter": { - "import": "./dist/ext/twitter.js", - "require": "./dist/ext/twitter.cjs", - "types": "./dist/ext/twitter.d.ts" - }, - "./hooks": { - "import": "./dist/hooks/index.js", - "require": "./dist/hooks/index.cjs", - "types": "./dist/hooks/index.d.ts" - }, - "./hooks/solana": { - "import": "./dist/hooks/solana/index.js", - "require": "./dist/hooks/solana/index.cjs", - "types": "./dist/hooks/solana/index.d.ts" - }, - ".": { - "import": "./dist/index.js", - "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" - }, - "./index.css": "./dist/index.css" - }, - "files": [ - "dist" - ], + "workspaces": ["packages/*"], "devDependencies": { - "@solana/actions-spec": "~2.2.0", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", - "autoprefixer": "^10.4.19", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.34.4", @@ -58,19 +30,6 @@ "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-tailwindcss": "^0.6.5", - "tailwindcss": "^3.4.3", - "tsup": "^8.2.0", "typescript": "^5.5.3" - }, - "peerDependencies": { - "@solana/wallet-adapter-react": "^0.15.0", - "@solana/wallet-adapter-react-ui": "^0.9.0", - "@solana/web3.js": "^1.95.1", - "react": ">=18", - "react-dom": ">=18" - }, - "dependencies": { - "clsx": "^2.1.1", - "nanoid": "^5.0.7" } } diff --git a/packages/blinks-core/package.json b/packages/blinks-core/package.json new file mode 100644 index 00000000..7c1e4075 --- /dev/null +++ b/packages/blinks-core/package.json @@ -0,0 +1,56 @@ +{ + "name": "@dialectlabs/blinks-core", + "version": "0.10.0", + "license": "Apache-2.0", + "private": false, + "sideEffects": true, + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/dialectlabs/blinks" + }, + "scripts": { + "build": "tsup-node", + "dev": "tsup-node --watch" + }, + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "devDependencies": { + "@solana/actions-spec": "~2.2.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.16.1", + "@typescript-eslint/parser": "^7.16.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.34.4", + "eslint-plugin-react-hooks": "^4.6.2", + "postcss": "^8.4.39", + "postcss-prefix-selector": "^1.16.1", + "prettier": "^3.3.3", + "prettier-plugin-organize-imports": "^4.0.0", + "prettier-plugin-tailwindcss": "^0.6.5", + "tailwindcss": "^3.4.3", + "tsup": "^8.2.0", + "typescript": "^5.5.3" + }, + "peerDependencies": { + "react": ">=18" + }, + "dependencies": { + "@solana/web3.js": "^1.95.1", + "nanoid": "^5.0.7" + } +} \ No newline at end of file diff --git a/src/ui/ActionContainer.tsx b/packages/blinks-core/src/BlinkContainer.tsx similarity index 70% rename from src/ui/ActionContainer.tsx rename to packages/blinks-core/src/BlinkContainer.tsx index f428c9dc..e23d371a 100644 --- a/src/ui/ActionContainer.tsx +++ b/packages/blinks-core/src/BlinkContainer.tsx @@ -1,4 +1,10 @@ -import { useEffect, useMemo, useReducer, useState } from 'react'; +import { + type ComponentType, + useEffect, + useMemo, + useReducer, + useState, +} from 'react'; import { AbstractActionComponent, Action, @@ -6,32 +12,63 @@ import { type ActionContext, type ActionPostResponse, type ActionSupportability, - ButtonActionComponent, - type ExtendedActionState, FormActionComponent, getExtendedActionState, getExtendedInterstitialState, getExtendedWebsiteState, - isParameterSelectable, - isPatternAllowed, mergeActionStates, MultiValueActionComponent, + type SecurityActionState, SingleValueActionComponent, -} from '../api'; -import { checkSecurity, type SecurityLevel } from '../shared'; -import { isInterstitial } from '../utils/interstitial-url.ts'; +} from './api'; +import { checkSecurity, isInterstitial, type SecurityLevel } from './utils'; import { isPostRequestError, isSignTransactionError, -} from '../utils/type-guards.ts'; -import { - ActionLayout, - type Disclaimer, - DisclaimerType, - type StylePreset, -} from './ActionLayout'; +} from './utils/type-guards.ts'; + +export type BlinkSecurityState = SecurityActionState; + +export enum DisclaimerType { + BLOCKED = 'blocked', + UNKNOWN = 'unknown', +} + +export type Disclaimer = + | { + type: DisclaimerType.BLOCKED; + ignorable: boolean; + hidden: boolean; + onSkip: () => void; + } + | { + type: DisclaimerType.UNKNOWN; + ignorable: boolean; + }; + +export interface BlinkCaption { + type: 'success' | 'error'; + text: string; +} + +export interface BaseBlinkLayoutProps { + id?: string; + securityState: BlinkSecurityState; + action: Action; + websiteUrl?: string | null; + websiteText?: string | null; + disclaimer?: Disclaimer | null; + caption?: BlinkCaption | null; + executeFn: ( + component: AbstractActionComponent, + params?: Record, + ) => Promise; + executionStatus: ExecutionStatus; + executingAction?: AbstractActionComponent | null; + supportability: ActionSupportability; +} -type ExecutionStatus = +export type ExecutionStatus = | 'blocked' | 'checking-supportability' | 'idle' @@ -39,7 +76,7 @@ type ExecutionStatus = | 'success' | 'error'; -interface ExecutionState { +export interface ExecutionState { status: ExecutionStatus; checkingSupportability?: boolean; executingAction?: AbstractActionComponent | null; @@ -47,7 +84,7 @@ interface ExecutionState { successMessage?: string | null; } -enum ExecutionType { +export enum ExecutionType { CHECK_SUPPORTABILITY = 'CHECK_SUPPORTABILITY', INITIATE = 'INITIATE', FINISH = 'FINISH', @@ -138,35 +175,14 @@ const executionReducer = ( } }; -const buttonVariantMap: Record< - ExecutionStatus, - 'default' | 'error' | 'success' -> = { - 'checking-supportability': 'default', - blocked: 'default', - idle: 'default', - executing: 'default', - success: 'success', - error: 'error', -}; - -const buttonLabelMap: Record = { - 'checking-supportability': 'Loading', - blocked: null, - idle: null, - executing: 'Executing', - success: 'Completed', - error: 'Failed', -}; - type ActionStateWithOrigin = | { - action: ExtendedActionState; + action: SecurityActionState; origin?: never; } | { - action: ExtendedActionState; - origin: ExtendedActionState; + action: SecurityActionState; + origin: SecurityActionState; originType: Source; }; @@ -208,34 +224,29 @@ const checkSecurityFromActionState = ( : true; }; -const SOFT_LIMIT_BUTTONS = 10; -const SOFT_LIMIT_INPUTS = 3; -const SOFT_LIMIT_FORM_INPUTS = 10; - const DEFAULT_SECURITY_LEVEL: SecurityLevel = 'only-trusted'; type Source = 'websites' | 'interstitials' | 'actions'; type NormalizedSecurityLevel = Record; +export interface BlinkContainerProps { + action: Action; + websiteUrl?: string | null; + websiteText?: string | null; + callbacks?: Partial; + securityLevel?: SecurityLevel | NormalizedSecurityLevel; + Layout: ComponentType; +} + // overall flow: check-supportability -> idle/block -> executing -> success/error or chain -export const ActionContainer = ({ +export const BlinkContainer = ({ action: initialAction, websiteUrl, websiteText, callbacks, securityLevel = DEFAULT_SECURITY_LEVEL, - stylePreset = 'default', - Experimental__ActionLayout = ActionLayout, -}: { - action: Action; - websiteUrl?: string | null; - websiteText?: string | null; - callbacks?: Partial; - securityLevel?: SecurityLevel | NormalizedSecurityLevel; - stylePreset?: StylePreset; - // please do not use it yet, better api is coming.. - Experimental__ActionLayout?: typeof ActionLayout; -}) => { + Layout, +}: BlinkContainerProps) => { const [action, setAction] = useState(initialAction); const normalizedSecurityLevel: NormalizedSecurityLevel = useMemo(() => { if (typeof securityLevel === 'string') { @@ -262,7 +273,7 @@ export const ActionContainer = ({ mergeActionStates( ...([actionState.action, actionState.origin].filter( Boolean, - ) as ExtendedActionState[]), + ) as SecurityActionState[]), ), [actionState], ); @@ -361,47 +372,6 @@ export const ActionContainer = ({ checkSupportability(action); }, [action, executionState.status, overallState, isPassingSecurityCheck]); - const buttons = useMemo( - () => - action?.actions - .filter((it) => it instanceof ButtonActionComponent) - .filter((it) => - executionState.executingAction - ? executionState.executingAction === it - : true, - ) - .toSpliced(SOFT_LIMIT_BUTTONS) ?? [], - [action, executionState.executingAction], - ); - const inputs = useMemo( - () => - action?.actions - .filter( - (it) => - it instanceof SingleValueActionComponent || - it instanceof MultiValueActionComponent, - ) - .filter((it) => - executionState.executingAction - ? executionState.executingAction === it - : true, - ) - .toSpliced(SOFT_LIMIT_INPUTS) ?? [], - [action, executionState.executingAction], - ); - const form = useMemo(() => { - const [formComponent] = - action?.actions - .filter((it) => it instanceof FormActionComponent) - .filter((it) => - executionState.executingAction - ? executionState.executingAction === it - : true, - ) ?? []; - - return formComponent; - }, [action, executionState.executingAction]); - const execute = async ( component: AbstractActionComponent, params?: Record, @@ -517,69 +487,6 @@ export const ActionContainer = ({ } }; - const asButtonProps = (it: ButtonActionComponent) => { - return { - text: buttonLabelMap[executionState.status] ?? it.label, - loading: - executionState.status === 'executing' && - it === executionState.executingAction, - disabled: - action.disabled || - action.type === 'completed' || - executionState.status !== 'idle', - variant: - buttonVariantMap[ - action.type === 'completed' ? 'success' : executionState.status - ], - onClick: (params?: Record) => - execute(it.parentComponent ?? it, params), - }; - }; - - const asInputProps = ( - it: SingleValueActionComponent | MultiValueActionComponent, - { placement }: { placement: 'form' | 'standalone' } = { - placement: 'standalone', - }, - ) => { - return { - type: it.parameter.type ?? 'text', - placeholder: it.parameter.label, - disabled: - action.disabled || - action.type === 'completed' || - executionState.status !== 'idle', - name: it.parameter.name, - required: it.parameter.required, - min: it.parameter.min, - max: it.parameter.max, - pattern: - it instanceof SingleValueActionComponent && - isPatternAllowed(it.parameter) - ? it.parameter.pattern - : undefined, - options: isParameterSelectable(it.parameter) - ? it.parameter.options - : undefined, - description: it.parameter.patternDescription, - button: - placement === 'standalone' - ? asButtonProps(it.toButtonActionComponent()) - : undefined, - }; - }; - - const asFormProps = (it: FormActionComponent) => { - return { - button: asButtonProps(it.toButtonActionComponent()), - inputs: it.parameters.toSpliced(SOFT_LIMIT_FORM_INPUTS).map((parameter) => - asInputProps(it.toInputActionComponent(parameter.name), { - placement: 'form', - }), - ), - }; - }; - const disclaimer: Disclaimer | null = useMemo(() => { if (overallState === 'malicious') { return { @@ -602,24 +509,32 @@ export const ActionContainer = ({ return null; }, [executionState.status, isPassingSecurityCheck, overallState]); + const blinkCaption: BlinkCaption | null = useMemo(() => { + if (executionState.status === 'error') { + return { type: 'error', text: executionState.errorMessage ?? '' }; + } + + if (executionState.status === 'success') { + return { type: 'success', text: executionState.successMessage ?? '' }; + } + + return null; + }, [ + executionState.status, + executionState.errorMessage, + executionState.successMessage, + ]); + return ( - asButtonProps(button))} - inputs={inputs.map((input) => asInputProps(input))} - form={form ? asFormProps(form) : undefined} + action={action} + caption={blinkCaption} + executionStatus={executionState.status} + executingAction={executionState.executingAction} + executeFn={execute} disclaimer={disclaimer} supportability={supportability} id={action.id} diff --git a/src/api/Action/Action.ts b/packages/blinks-core/src/api/Action/Action.ts similarity index 98% rename from src/api/Action/Action.ts rename to packages/blinks-core/src/api/Action/Action.ts index f4f55771..78029ee6 100644 --- a/src/api/Action/Action.ts +++ b/packages/blinks-core/src/api/Action/Action.ts @@ -1,6 +1,6 @@ import { nanoid } from 'nanoid'; -import { isUrlSameOrigin } from '../../shared'; -import { proxify, proxifyImage } from '../../utils/proxify.ts'; +import { proxify, proxifyImage } from '../../utils'; +import { isUrlSameOrigin } from '../../utils/security.ts'; import type { ActionAdapter } from '../ActionConfig.ts'; import type { ActionParameterType, diff --git a/src/api/Action/action-components/AbstractActionComponent.ts b/packages/blinks-core/src/api/Action/action-components/AbstractActionComponent.ts similarity index 96% rename from src/api/Action/action-components/AbstractActionComponent.ts rename to packages/blinks-core/src/api/Action/action-components/AbstractActionComponent.ts index f93c2cad..86a62fbd 100644 --- a/src/api/Action/action-components/AbstractActionComponent.ts +++ b/packages/blinks-core/src/api/Action/action-components/AbstractActionComponent.ts @@ -1,4 +1,4 @@ -import { proxify } from '../../../utils/proxify.ts'; +import { proxify } from '../../../utils'; import type { ActionError, ActionPostRequest, diff --git a/src/api/Action/action-components/ButtonActionComponent.ts b/packages/blinks-core/src/api/Action/action-components/ButtonActionComponent.ts similarity index 100% rename from src/api/Action/action-components/ButtonActionComponent.ts rename to packages/blinks-core/src/api/Action/action-components/ButtonActionComponent.ts diff --git a/src/api/Action/action-components/FormActionComponent.ts b/packages/blinks-core/src/api/Action/action-components/FormActionComponent.ts similarity index 100% rename from src/api/Action/action-components/FormActionComponent.ts rename to packages/blinks-core/src/api/Action/action-components/FormActionComponent.ts diff --git a/src/api/Action/action-components/MultiValueActionComponent.ts b/packages/blinks-core/src/api/Action/action-components/MultiValueActionComponent.ts similarity index 100% rename from src/api/Action/action-components/MultiValueActionComponent.ts rename to packages/blinks-core/src/api/Action/action-components/MultiValueActionComponent.ts diff --git a/src/api/Action/action-components/SingleValueActionComponent.ts b/packages/blinks-core/src/api/Action/action-components/SingleValueActionComponent.ts similarity index 100% rename from src/api/Action/action-components/SingleValueActionComponent.ts rename to packages/blinks-core/src/api/Action/action-components/SingleValueActionComponent.ts diff --git a/src/api/Action/action-components/guards.ts b/packages/blinks-core/src/api/Action/action-components/guards.ts similarity index 100% rename from src/api/Action/action-components/guards.ts rename to packages/blinks-core/src/api/Action/action-components/guards.ts diff --git a/src/api/Action/action-components/index.ts b/packages/blinks-core/src/api/Action/action-components/index.ts similarity index 100% rename from src/api/Action/action-components/index.ts rename to packages/blinks-core/src/api/Action/action-components/index.ts diff --git a/src/api/Action/action-supportability.ts b/packages/blinks-core/src/api/Action/action-supportability.ts similarity index 100% rename from src/api/Action/action-supportability.ts rename to packages/blinks-core/src/api/Action/action-supportability.ts diff --git a/src/api/Action/index.ts b/packages/blinks-core/src/api/Action/index.ts similarity index 100% rename from src/api/Action/index.ts rename to packages/blinks-core/src/api/Action/index.ts diff --git a/src/api/ActionCallbacks.ts b/packages/blinks-core/src/api/ActionCallbacks.ts similarity index 100% rename from src/api/ActionCallbacks.ts rename to packages/blinks-core/src/api/ActionCallbacks.ts diff --git a/src/api/ActionConfig.ts b/packages/blinks-core/src/api/ActionConfig.ts similarity index 94% rename from src/api/ActionConfig.ts rename to packages/blinks-core/src/api/ActionConfig.ts index 03dcdf06..76fdf72f 100644 --- a/src/api/ActionConfig.ts +++ b/packages/blinks-core/src/api/ActionConfig.ts @@ -1,7 +1,9 @@ import { Connection } from '@solana/web3.js'; -import { type Action } from './Action'; -import { AbstractActionComponent } from './Action/action-components'; -import { DEFAULT_SUPPORTED_BLOCKCHAIN_IDS } from './Action/action-supportability.ts'; +import { + type Action, + AbstractActionComponent, + DEFAULT_SUPPORTED_BLOCKCHAIN_IDS, +} from './Action'; export interface ActionContext { originalUrl: string; diff --git a/src/api/ActionsRegistry.ts b/packages/blinks-core/src/api/ActionsRegistry.ts similarity index 95% rename from src/api/ActionsRegistry.ts rename to packages/blinks-core/src/api/ActionsRegistry.ts index b8baa60d..24ec59bd 100644 --- a/src/api/ActionsRegistry.ts +++ b/packages/blinks-core/src/api/ActionsRegistry.ts @@ -140,11 +140,11 @@ export interface RegisteredEntity { state: 'trusted' | 'malicious'; } -export type ExtendedActionState = RegisteredEntity['state'] | 'unknown'; +export type SecurityActionState = RegisteredEntity['state'] | 'unknown'; export const mergeActionStates = ( - ...states: ExtendedActionState[] -): ExtendedActionState => { + ...states: SecurityActionState[] +): SecurityActionState => { if (states.includes('malicious')) { return 'malicious'; } @@ -158,7 +158,7 @@ export const mergeActionStates = ( export const getExtendedActionState = ( actionOrUrl: Action | string, -): ExtendedActionState => { +): SecurityActionState => { return ( ActionsRegistry.getInstance().lookup( typeof actionOrUrl === 'string' ? actionOrUrl : actionOrUrl.url, @@ -167,7 +167,7 @@ export const getExtendedActionState = ( ); }; -export const getExtendedWebsiteState = (url: string): ExtendedActionState => { +export const getExtendedWebsiteState = (url: string): SecurityActionState => { return ( ActionsRegistry.getInstance().lookup(url, 'website')?.state ?? 'unknown' ); @@ -175,7 +175,7 @@ export const getExtendedWebsiteState = (url: string): ExtendedActionState => { export const getExtendedInterstitialState = ( url: string, -): ExtendedActionState => { +): SecurityActionState => { return ( ActionsRegistry.getInstance().lookup(url, 'interstitial')?.state ?? 'unknown' diff --git a/src/api/actions-spec.ts b/packages/blinks-core/src/api/actions-spec.ts similarity index 100% rename from src/api/actions-spec.ts rename to packages/blinks-core/src/api/actions-spec.ts diff --git a/src/api/index.ts b/packages/blinks-core/src/api/index.ts similarity index 100% rename from src/api/index.ts rename to packages/blinks-core/src/api/index.ts diff --git a/src/api/solana-pay-spec.ts b/packages/blinks-core/src/api/solana-pay-spec.ts similarity index 100% rename from src/api/solana-pay-spec.ts rename to packages/blinks-core/src/api/solana-pay-spec.ts diff --git a/packages/blinks-core/src/index.ts b/packages/blinks-core/src/index.ts new file mode 100644 index 00000000..51b61abb --- /dev/null +++ b/packages/blinks-core/src/index.ts @@ -0,0 +1,3 @@ +export * from './api'; +export * from './BlinkContainer'; +export * from './utils'; diff --git a/src/utils/caip-2.ts b/packages/blinks-core/src/utils/caip-2.ts similarity index 100% rename from src/utils/caip-2.ts rename to packages/blinks-core/src/utils/caip-2.ts diff --git a/src/utils/constants.ts b/packages/blinks-core/src/utils/constants.ts similarity index 68% rename from src/utils/constants.ts rename to packages/blinks-core/src/utils/constants.ts index 413fe615..3ca3cba1 100644 --- a/src/utils/constants.ts +++ b/packages/blinks-core/src/utils/constants.ts @@ -1,2 +1 @@ -export const noop = () => {}; export const SOLANA_ACTION_PREFIX = /^(solana-action:|solana:)/; diff --git a/src/utils/dependency-versions.ts b/packages/blinks-core/src/utils/dependency-versions.ts similarity index 100% rename from src/utils/dependency-versions.ts rename to packages/blinks-core/src/utils/dependency-versions.ts diff --git a/packages/blinks-core/src/utils/index.ts b/packages/blinks-core/src/utils/index.ts new file mode 100644 index 00000000..3ccbb43c --- /dev/null +++ b/packages/blinks-core/src/utils/index.ts @@ -0,0 +1,5 @@ +export { BlockchainIds } from './caip-2.ts'; +export * from './interstitial-url.ts'; +export { proxify, proxifyImage, setProxyUrl } from './proxify'; +export { checkSecurity, type SecurityLevel } from './security'; +export * from './url-mapper.ts'; diff --git a/src/utils/interstitial-url.ts b/packages/blinks-core/src/utils/interstitial-url.ts similarity index 100% rename from src/utils/interstitial-url.ts rename to packages/blinks-core/src/utils/interstitial-url.ts diff --git a/src/utils/proxify.ts b/packages/blinks-core/src/utils/proxify.ts similarity index 100% rename from src/utils/proxify.ts rename to packages/blinks-core/src/utils/proxify.ts diff --git a/src/shared/security.ts b/packages/blinks-core/src/utils/security.ts similarity index 86% rename from src/shared/security.ts rename to packages/blinks-core/src/utils/security.ts index 49bb6d5b..0ebc293d 100644 --- a/src/shared/security.ts +++ b/packages/blinks-core/src/utils/security.ts @@ -1,9 +1,9 @@ -import type { ExtendedActionState } from '../api'; +import type { SecurityActionState } from '../api'; export type SecurityLevel = 'only-trusted' | 'non-malicious' | 'all'; export const checkSecurity = ( - state: ExtendedActionState, + state: SecurityActionState, securityLevel: SecurityLevel, ): boolean => { switch (securityLevel) { diff --git a/src/utils/type-guards.ts b/packages/blinks-core/src/utils/type-guards.ts similarity index 100% rename from src/utils/type-guards.ts rename to packages/blinks-core/src/utils/type-guards.ts diff --git a/src/utils/url-mapper.ts b/packages/blinks-core/src/utils/url-mapper.ts similarity index 100% rename from src/utils/url-mapper.ts rename to packages/blinks-core/src/utils/url-mapper.ts diff --git a/test/api/Action/action-supportability.spec.ts b/packages/blinks-core/test/api/Action/action-supportability.spec.ts similarity index 100% rename from test/api/Action/action-supportability.spec.ts rename to packages/blinks-core/test/api/Action/action-supportability.spec.ts diff --git a/test/api/action-registry.spec.ts b/packages/blinks-core/test/api/action-registry.spec.ts similarity index 98% rename from test/api/action-registry.spec.ts rename to packages/blinks-core/test/api/action-registry.spec.ts index 3dbb8730..d3dd3fca 100644 --- a/test/api/action-registry.spec.ts +++ b/packages/blinks-core/test/api/action-registry.spec.ts @@ -1,8 +1,5 @@ import { describe, expect, test } from 'bun:test'; -import { - ActionsRegistry, - type ActionsRegistryConfig, -} from '../../src/api/ActionsRegistry.ts'; +import { ActionsRegistry, type ActionsRegistryConfig } from '../../src'; describe('ActionsRegistry', () => { describe('actions lookup', () => { diff --git a/test/utils/dependency-versions.spec.ts b/packages/blinks-core/test/utils/dependency-versions.spec.ts similarity index 100% rename from test/utils/dependency-versions.spec.ts rename to packages/blinks-core/test/utils/dependency-versions.spec.ts diff --git a/test/utils/interstitial-url.spec.ts b/packages/blinks-core/test/utils/interstitial-url.spec.ts similarity index 98% rename from test/utils/interstitial-url.spec.ts rename to packages/blinks-core/test/utils/interstitial-url.spec.ts index 44814c46..6d97893a 100644 --- a/test/utils/interstitial-url.spec.ts +++ b/packages/blinks-core/test/utils/interstitial-url.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test'; -import { isInterstitial } from '../../src/utils/interstitial-url.ts'; +import { isInterstitial } from '../../src'; describe('isInterstitialUrl', () => { test('should return true and decode the action URL for a valid interstitial URL with solana-action: prefix (URL-encoded)', () => { diff --git a/test/utils/url-mapper.spec.ts b/packages/blinks-core/test/utils/url-mapper.spec.ts similarity index 99% rename from test/utils/url-mapper.spec.ts rename to packages/blinks-core/test/utils/url-mapper.spec.ts index e6da2945..a5b7d90b 100644 --- a/test/utils/url-mapper.spec.ts +++ b/packages/blinks-core/test/utils/url-mapper.spec.ts @@ -1,8 +1,5 @@ import { describe, expect, test } from 'bun:test'; -import { - ActionsURLMapper, - type ActionsJsonConfig, -} from '../../src/utils/url-mapper.ts'; +import { ActionsURLMapper, type ActionsJsonConfig } from '../../src'; describe('ActionsURLMapper', () => { describe('Exact match rules', () => { diff --git a/packages/blinks-core/tsconfig.json b/packages/blinks-core/tsconfig.json new file mode 100644 index 00000000..238655f2 --- /dev/null +++ b/packages/blinks-core/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/blinks-core/tsup.config.ts b/packages/blinks-core/tsup.config.ts new file mode 100644 index 00000000..c95691fb --- /dev/null +++ b/packages/blinks-core/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig, type Options } from 'tsup'; + +const commonCfg: Partial = { + splitting: true, + sourcemap: false, + clean: true, + format: ['cjs', 'esm'], + target: ['esnext'], +}; + +export default defineConfig([ + { + ...commonCfg, + entry: ['src/index.ts'], + dts: { + entry: ['src/index.ts'], + }, + }, +]); diff --git a/packages/blinks/package.json b/packages/blinks/package.json new file mode 100644 index 00000000..08990d35 --- /dev/null +++ b/packages/blinks/package.json @@ -0,0 +1,76 @@ +{ + "name": "@dialectlabs/blinks", + "version": "0.9.3", + "license": "Apache-2.0", + "private": false, + "sideEffects": true, + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/dialectlabs/blinks" + }, + "scripts": { + "build": "tsup-node", + "dev": "tsup-node --watch" + }, + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + "./ext/twitter": { + "import": "./dist/ext/twitter.js", + "require": "./dist/ext/twitter.cjs", + "types": "./dist/ext/twitter.d.ts" + }, + "./hooks": { + "import": "./dist/hooks/index.js", + "require": "./dist/hooks/index.cjs", + "types": "./dist/hooks/index.d.ts" + }, + "./hooks/solana": { + "import": "./dist/hooks/solana/index.js", + "require": "./dist/hooks/solana/index.cjs", + "types": "./dist/hooks/solana/index.d.ts" + }, + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + }, + "./index.css": "./dist/index.css" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@solana/actions-spec": "~2.2.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.16.1", + "@typescript-eslint/parser": "^7.16.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.34.4", + "eslint-plugin-react-hooks": "^4.6.2", + "postcss": "^8.4.39", + "postcss-prefix-selector": "^1.16.1", + "prettier": "^3.3.3", + "prettier-plugin-organize-imports": "^4.0.0", + "prettier-plugin-tailwindcss": "^0.6.5", + "tailwindcss": "^3.4.3", + "tsup": "^8.2.0", + "typescript": "^5.5.3" + }, + "peerDependencies": { + "@solana/wallet-adapter-react": "^0.15.0", + "@solana/wallet-adapter-react-ui": "^0.9.0", + "@solana/web3.js": "^1.95.1", + "react": ">=18", + "react-dom": ">=18" + }, + "dependencies": { + "clsx": "^2.1.1", + "@dialectlabs/blinks-core": "0.10.0" + } +} diff --git a/src/ext/index.ts b/packages/blinks/src/ext/index.ts similarity index 100% rename from src/ext/index.ts rename to packages/blinks/src/ext/index.ts diff --git a/src/ext/twitter.tsx b/packages/blinks/src/ext/twitter.tsx similarity index 95% rename from src/ext/twitter.tsx rename to packages/blinks/src/ext/twitter.tsx index 1f6b669b..90c08c75 100644 --- a/src/ext/twitter.tsx +++ b/packages/blinks/src/ext/twitter.tsx @@ -1,24 +1,27 @@ -import { createRoot } from 'react-dom/client'; import { Action, type ActionAdapter, type ActionCallbacksConfig, + type ActionsJsonConfig, ActionsRegistry, type ActionSupportStrategy, + ActionsURLMapper, + checkSecurity, defaultActionSupportStrategy, getExtendedActionState, getExtendedInterstitialState, getExtendedWebsiteState, -} from '../api'; -import { checkSecurity, type SecurityLevel } from '../shared'; -import { ActionContainer, type StylePreset } from '../ui'; -import { noop } from '../utils/constants'; -import { isInterstitial } from '../utils/interstitial-url.ts'; -import { proxify } from '../utils/proxify.ts'; -import { type ActionsJsonConfig, ActionsURLMapper } from '../utils/url-mapper'; + isInterstitial, + proxify, + type SecurityLevel, +} from '@dialectlabs/blinks-core'; +import { createRoot } from 'react-dom/client'; +import { Blink, type StylePreset } from '../ui'; type ObserverSecurityLevel = SecurityLevel; +const noop = () => {}; + export interface ObserverOptions { // trusted > unknown > malicious securityLevel: @@ -241,7 +244,7 @@ function createAction({ actionRoot.render(
e.stopPropagation()}> - { ignore = true; }; - }, [actionApiUrl, isRegistryLoaded]); + }, [actionApiUrl, isRegistryLoaded, supportStrategy]); useEffect(() => { action?.setAdapter(adapter); diff --git a/src/hooks/useActionRegistryInterval.ts b/packages/blinks/src/hooks/useActionRegistryInterval.ts similarity index 85% rename from src/hooks/useActionRegistryInterval.ts rename to packages/blinks/src/hooks/useActionRegistryInterval.ts index 037c17e9..e7adaf86 100644 --- a/src/hooks/useActionRegistryInterval.ts +++ b/packages/blinks/src/hooks/useActionRegistryInterval.ts @@ -1,6 +1,6 @@ 'use client'; +import { ActionsRegistry } from '@dialectlabs/blinks-core'; import { useEffect, useState } from 'react'; -import { ActionsRegistry } from '../api'; export function useActionsRegistryInterval() { const [isRegistryLoaded, setRegistryLoaded] = useState(false); diff --git a/src/index.css b/packages/blinks/src/index.css similarity index 100% rename from src/index.css rename to packages/blinks/src/index.css diff --git a/packages/blinks/src/index.ts b/packages/blinks/src/index.ts new file mode 100644 index 00000000..f85dc0b9 --- /dev/null +++ b/packages/blinks/src/index.ts @@ -0,0 +1,3 @@ +export * from '@dialectlabs/blinks-core'; + +export * from './ui'; diff --git a/src/ui/ActionLayout.tsx b/packages/blinks/src/ui/BaseBlinkLayout.tsx similarity index 78% rename from src/ui/ActionLayout.tsx rename to packages/blinks/src/ui/BaseBlinkLayout.tsx index 6cd6ef7b..6b42deb7 100644 --- a/src/ui/ActionLayout.tsx +++ b/packages/blinks/src/ui/BaseBlinkLayout.tsx @@ -1,55 +1,48 @@ +import { + type ActionSupportability, + type Disclaimer, + DisclaimerType, + type SecurityActionState, +} from '@dialectlabs/blinks-core'; import clsx from 'clsx'; import { type ReactNode, useState } from 'react'; -import type { ActionSupportability, ExtendedActionState } from '../api'; -import { Badge } from './Badge.tsx'; -import { Snackbar } from './Snackbar.tsx'; -import { ExclamationShieldIcon, InfoShieldIcon, LinkIcon } from './icons'; -import ConfigIcon from './icons/ConfigIcon.tsx'; +import { Badge } from './internal/Badge.tsx'; +import { Snackbar } from './internal/Snackbar.tsx'; +import { + ConfigIcon, + ExclamationShieldIcon, + InfoShieldIcon, + LinkIcon, +} from './internal/icons'; import { ActionButton, + ActionCheckboxGroup, ActionDateInput, ActionEmailInput, ActionNumberInput, ActionRadioGroup, ActionSelect, + ActionTextArea, ActionTextInput, ActionUrlInput, -} from './inputs'; -import { ActionCheckboxGroup } from './inputs/ActionCheckboxGroup.tsx'; -import { ActionTextArea } from './inputs/ActionTextArea.tsx'; -import type { BaseButtonProps, BaseInputProps } from './inputs/types.ts'; +} from './internal/inputs'; +import type { + BaseButtonProps, + BaseInputProps, +} from './internal/inputs/types.ts'; +import type { StylePreset } from './types.ts'; -type ActionType = ExtendedActionState; type ButtonProps = BaseButtonProps; type InputProps = BaseInputProps; -export type StylePreset = 'default' | 'x-dark' | 'x-light' | 'custom'; - -export enum DisclaimerType { - BLOCKED = 'blocked', - UNKNOWN = 'unknown', -} - -export type Disclaimer = - | { - type: DisclaimerType.BLOCKED; - ignorable: boolean; - hidden: boolean; - onSkip: () => void; - } - | { - type: DisclaimerType.UNKNOWN; - ignorable: boolean; - }; - -const stylePresetClassMap: Record = { +const themeClassMap: Record = { default: 'dial-light', 'x-dark': 'x-dark', 'x-light': 'x-light', custom: 'custom', }; -interface LayoutProps { +export interface InnerLayoutProps { stylePreset?: StylePreset; image?: string; error?: string | null; @@ -57,7 +50,7 @@ interface LayoutProps { websiteUrl?: string | null; websiteText?: string | null; disclaimer?: Disclaimer | null; - type: ActionType; + securityState: SecurityActionState; title: string; description: string; buttons?: ButtonProps[]; @@ -105,7 +98,7 @@ const NotSupportedBlock = ({
@@ -157,7 +150,7 @@ const DisclaimerBlock = ({

{ignorable && onSkip && (