@capitalos/vue 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -13
package/dist/index.js
CHANGED
|
@@ -187,7 +187,7 @@ function useCapitalOsAuth() {
|
|
|
187
187
|
|
|
188
188
|
//#endregion
|
|
189
189
|
//#region package.json
|
|
190
|
-
var version = "0.
|
|
190
|
+
var version = "0.2.0";
|
|
191
191
|
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/composables/use-iframe-component.ts
|
|
@@ -392,7 +392,8 @@ const Onboarding = (0, vue.defineComponent)({
|
|
|
392
392
|
allowExitOnNonApproved: {
|
|
393
393
|
type: Boolean,
|
|
394
394
|
default: void 0
|
|
395
|
-
}
|
|
395
|
+
},
|
|
396
|
+
doneButtonText: String
|
|
396
397
|
},
|
|
397
398
|
emits: {
|
|
398
399
|
...commonEmits,
|
|
@@ -406,7 +407,12 @@ const Onboarding = (0, vue.defineComponent)({
|
|
|
406
407
|
entryPoint: "onboarding",
|
|
407
408
|
onboardingEntryPoint: props.entryPoint,
|
|
408
409
|
onboardingExitPoint: props.exitPoint,
|
|
409
|
-
allowExitOnNonApproved: props.allowExitOnNonApproved
|
|
410
|
+
allowExitOnNonApproved: props.allowExitOnNonApproved,
|
|
411
|
+
copy: props.doneButtonText ? {
|
|
412
|
+
"onboarding.doneButton": props.doneButtonText,
|
|
413
|
+
"onboarding.continueButton": props.doneButtonText,
|
|
414
|
+
"activation.successButton": props.doneButtonText
|
|
415
|
+
} : void 0
|
|
410
416
|
}),
|
|
411
417
|
componentCallbacks: { onboarding: { onDone: (account) => {
|
|
412
418
|
emit("done", account);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["globalState: CapitalOsState | null","error: unknown","appOrVue: Vue2Constructor | Vue3App","config: CapitalOsConfig","logger","activeAbortController: AbortController | null","TokenType","TokenParamKey","TokenParamLocation","state: CapitalOsState","error: CapitalOSError","CapitalOSError","options: UseIframeComponentOptions","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","IframeManager","SDK_VERSION","rawError: RawErrorDetails","CapitalOSError","el: Element | null","_account: Account","account: Account"],"sources":["../src/plugin.ts","../src/components/common-props.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/composables/use-iframe-component.ts","../src/components/CardsApp.ts","../src/components/BillPayApp.ts","../src/components/Onboarding.ts"],"sourcesContent":["// =============================================================================\n// MAINTAINER NOTE: Vue 2.7 vs Vue 3 Plugin Compatibility\n// =============================================================================\n//\n// Vue 2.7 and Vue 3 call plugin.install() with DIFFERENT arguments:\n//\n// Vue 3 consumer code:\n// const app = createApp(App)\n// app.use(myPlugin) // → calls myPlugin.install(app) with app INSTANCE\n//\n// Vue 2.7 consumer code:\n// Vue.use(myPlugin) // → calls myPlugin.install(Vue) with Vue CONSTRUCTOR\n//\n// This matters because:\n// - Vue 3's app instance has app.provide() for dependency injection\n// - Vue 2.7's Vue constructor does NOT have provide() - must use Vue.mixin()\n//\n// This plugin detects which API is available and uses the appropriate method.\n// As an additional fallback for edge cases, we also store state in a module-level\n// variable that `useCapitalOsAuth` can access if `inject()` returns undefined.\n//\n// FUTURE: If we ever need to support Vue < 2.7 or have more complex version-\n// specific logic throughout the codebase, consider using `vue-demi` package\n// (https://github.com/vueuse/vue-demi) which abstracts all Vue 2/3 differences.\n// For now, our simple detection is sufficient since Vue 2.7 has Composition API\n// backported and we only need to handle the plugin install signature difference.\n// =============================================================================\n\nimport { ref, readonly } from 'vue'\nimport {\n exchangeOneTimeToken,\n createLogger,\n TokenType,\n TokenParamKey,\n TokenParamLocation,\n type TokenData,\n} from '@capitalos/core'\nimport type { CapitalOsConfig, CapitalOsState } from './types'\n\n/**\n * Injection key for CapitalOS state.\n * Used with Vue's provide/inject system.\n */\nexport const CAPITALOS_INJECTION_KEY = Symbol('capitalos')\n\n// MAINTAINER NOTE: Module-level state storage for Vue 2.7 compatibility fallback.\n//\n// In Vue 2.7, even with the mixin approach, there can be edge cases where\n// `inject()` doesn't find the provided value (e.g., in the root component\n// or components created before the mixin is applied). This global fallback\n// ensures `useCapitalOsAuth()` always has access to the state.\nlet globalState: CapitalOsState | null = null\n\n// @internal - used by useCapitalOsAuth for Vue 2.7 fallback\nexport function getGlobalState(): CapitalOsState | null {\n return globalState\n}\n\nconst TOKEN_EXCHANGE_TIMEOUT_MS = 10_000\n\n// Converts an unknown error to an Error object\nfunction toError(error: unknown): Error {\n if (error instanceof Error) {\n return error\n }\n return new Error(String(error))\n}\n\n// MAINTAINER NOTE: Module-level guard against double installation (singleton pattern).\n//\n// This MUST be module-level (not inside createCapitalOs) because:\n// - The plugin creates reactive state that should only exist once\n// - Multiple installations would create multiple token exchange iframes\n// - Multiple auth states would cause inconsistent behavior\n//\n// If a consumer accidentally calls Vue.use(createCapitalOs(...)) twice,\n// the second call will be silently ignored with a warning.\nlet installed = false\n\n// Type for Vue 2 constructor passed to plugin install.\n// In Vue 2.7, `Vue.use(plugin)` passes the Vue constructor.\ninterface Vue2Constructor {\n mixin: (mixin: Record<string, unknown>) => void\n version: string\n}\n\n// Type for Vue 3 app instance passed to plugin install.\n// In Vue 3, `app.use(plugin)` passes the app instance.\ninterface Vue3App {\n provide: (key: symbol, value: unknown) => void\n version: string\n}\n\n// Detect Vue version by checking for Vue 3's `provide` method.\n// Vue 3 app instances have `app.provide()`, Vue 2 constructors don't.\nfunction isVue3App(appOrVue: Vue2Constructor | Vue3App): appOrVue is Vue3App {\n return typeof (appOrVue as Vue3App).provide === 'function'\n}\n\n/**\n * Creates the CapitalOS Vue plugin.\n *\n * Usage (Vue 3):\n * ```typescript\n * import { createApp } from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * const app = createApp(App)\n * app.use(createCapitalOs({ getToken: async () => { ... } }))\n * app.mount('#app')\n * ```\n *\n * Usage (Vue 2.7):\n * ```typescript\n * import Vue from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * Vue.use(createCapitalOs({ getToken: async () => { ... } }))\n * new Vue({ render: h => h(App) }).$mount('#app')\n * ```\n */\nexport function createCapitalOs(config: CapitalOsConfig) {\n return {\n install(appOrVue: Vue2Constructor | Vue3App) {\n // Prevent double installation\n if (installed) {\n const logger = createLogger(config.enableLogging, config.logger)\n logger.warn('[CapitalOS] Plugin already installed, skipping duplicate installation')\n return\n }\n installed = true\n\n const logger = createLogger(config.enableLogging, config.logger)\n\n // Create reactive state\n const tokenData = ref<TokenData | undefined>(undefined)\n const isLoading = ref(false)\n const error = ref<Error | undefined>(undefined)\n\n // AbortController for cancelling in-flight token exchanges\n let activeAbortController: AbortController | null = null\n\n // Refreshes the token by fetching a new one-time token and exchanging it.\n async function refreshToken(): Promise<void> {\n // Prevent concurrent refresh calls - if already loading, skip\n if (isLoading.value) {\n logger.log('[CapitalOS] Token refresh already in progress, skipping')\n return\n }\n\n logger.log('[CapitalOS] Starting token refresh')\n isLoading.value = true\n error.value = undefined\n\n // Abort any in-flight exchange when a new one begins\n activeAbortController?.abort()\n const abortController = new AbortController()\n activeAbortController = abortController\n\n try {\n const oneTimeToken = await config.getToken()\n logger.log('[CapitalOS] Received one-time token')\n\n // Check if aborted after getToken\n if (abortController.signal.aborted) {\n return\n }\n\n // Exchange for long-lived token using @capitalos/core\n const result = await exchangeOneTimeToken({\n oneTimeToken,\n enableLogging: config.enableLogging,\n logger: config.logger,\n timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,\n signal: abortController.signal,\n })\n\n // Check if aborted after exchange\n if (abortController.signal.aborted) {\n return\n }\n\n tokenData.value = {\n token: result.longLivedToken,\n tokenType: TokenType.longLived,\n baseUrl: result.baseUrl,\n paramKey: TokenParamKey.accessToken,\n paramLocation: TokenParamLocation.hash,\n }\n logger.log('[CapitalOS] Token exchange complete')\n } catch (e) {\n // Ignore abort errors\n if (e instanceof Error && e.name === 'AbortError') {\n return\n }\n\n const err = toError(e)\n logger.error(`[CapitalOS] Token refresh failed: ${err.message}`)\n error.value = err\n } finally {\n if (activeAbortController === abortController) {\n activeAbortController = null\n }\n isLoading.value = false\n }\n }\n\n // Invalidates the current token, triggering a refresh.\n // Called when the iframe signals that the token has expired.\n function invalidateToken(): void {\n logger.log('[CapitalOS] Invalidating token')\n tokenData.value = undefined\n error.value = undefined\n refreshToken()\n }\n\n // Create state object to provide\n const state: CapitalOsState = {\n tokenData: readonly(tokenData),\n isLoading: readonly(isLoading),\n error: readonly(error),\n invalidateToken,\n config,\n }\n\n // Store globally as fallback for Vue 2.7 edge cases where inject() fails\n globalState = state\n\n // Provide state using the appropriate API based on Vue version\n if (isVue3App(appOrVue)) {\n // Vue 3: use app.provide() - this makes state available to all components\n // via inject(CAPITALOS_INJECTION_KEY)\n appOrVue.provide(CAPITALOS_INJECTION_KEY, state)\n } else {\n // Vue 2.7: use Vue.mixin() to add a provide function to all components.\n // This is the Vue 2 equivalent of app.provide() - it injects the provide\n // option into every component so inject() can find our state.\n appOrVue.mixin({\n provide() {\n return {\n [CAPITALOS_INJECTION_KEY]: state,\n }\n },\n })\n }\n\n // Start token exchange eagerly (matches Angular behavior)\n refreshToken()\n },\n }\n}\n","import type { PropType } from 'vue'\nimport type { ThemeColorScheme } from '@capitalos/core'\nimport { CapitalOSError } from '@capitalos/core'\n\n/**\n * Common props shared by all CapitalOS Vue components.\n */\nexport const commonProps = {\n /**\n * Theme color scheme for the interface.\n * Overrides the theme set in plugin configuration.\n */\n theme: String as PropType<ThemeColorScheme>,\n /**\n * Offset in pixels to add to the iframe height calculation.\n * Useful for avoiding scrollbars when the content height changes dynamically.\n */\n heightOffsetPx: {\n type: Number,\n default: 12,\n },\n /**\n * Whether to enable logging for debugging purposes.\n * Overrides the enableLogging set in plugin configuration.\n */\n enableLogging: Boolean,\n}\n\n/**\n * Common emits shared by all CapitalOS Vue components.\n */\nexport const commonEmits = {\n /**\n * Emitted when the interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n}\n","import { inject, computed, type ComputedRef } from 'vue'\nimport { CAPITALOS_INJECTION_KEY, getGlobalState } from '../plugin'\nimport type { CapitalOsState, AuthState } from '../types'\n\n/**\n * Return type for useCapitalOsAuth composable\n */\nexport interface UseCapitalOsAuthReturn extends CapitalOsState {\n /**\n * Computed authentication state\n */\n authState: ComputedRef<AuthState>\n}\n\n/**\n * @internal\n * Internal composable for accessing CapitalOS authentication state.\n * Used by SDK components - not intended for direct consumer use.\n */\nexport function useCapitalOsAuth(): UseCapitalOsAuthReturn {\n // MAINTAINER NOTE: Vue 2.7 vs Vue 3 Compatibility\n //\n // Try inject() first - this works in:\n // - Vue 3 (via app.provide)\n // - Vue 2.7 (via mixin provide, in most components)\n //\n // Fall back to getGlobalState() for Vue 2.7 edge cases where inject() fails,\n // such as the root component or components created before the mixin is applied.\n //\n // Do NOT remove the ?? getGlobalState() fallback - it's required for Vue 2.7.\n const state = inject<CapitalOsState>(CAPITALOS_INJECTION_KEY) ?? getGlobalState()\n\n if (!state) {\n throw new Error(\n 'useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. ' +\n 'Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts'\n )\n }\n\n const authState = computed<AuthState>(() => {\n if (state.isLoading.value) return 'loading'\n if (state.error.value) return 'error'\n if (state.tokenData.value) return 'authenticated'\n return 'idle'\n })\n\n return {\n ...state,\n authState,\n }\n}\n","{\n \"name\": \"@capitalos/vue\",\n \"version\": \"0.1.0\",\n \"description\": \"Vue SDK for CapitalOS\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n }\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"tsdown --watch\",\n \"lint\": \"eslint src --ext .ts --fix\",\n \"lint-ci\": \"eslint src --ext .ts\",\n \"start\": \"pnpm --filter vue-test-app dev\",\n \"start:all\": \"concurrently \\\"pnpm --filter sdk-test-server start\\\" \\\"pnpm start\\\"\",\n \"test:pack\": \"pnpm build && pnpm pack\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/CapitalOS/theboss\",\n \"directory\": \"sdk/vue\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/CapitalOS/theboss/issues\"\n },\n \"author\": \"CapitalOS\",\n \"license\": \"MIT\",\n \"peerDependencies\": {\n \"vue\": \"^2.7.0 || ^3.0.0\"\n },\n \"dependencies\": {\n \"@capitalos/core\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"tsdown\": \"^0.9.2\",\n \"typescript\": \"^5.6.2\",\n \"vue\": \"^3.5.13\",\n \"concurrently\": \"^9.1.2\"\n }\n}\n","import { ref, watch, onUnmounted, h, type VNode, type Ref } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type TokenData,\n type RawErrorDetails,\n type RenderingContext,\n type IframeManagerConfig,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from './use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\n\n/**\n * Props expected by useIframeComponent - matches commonProps shape\n */\nexport interface IframeComponentProps {\n theme?: 'light' | 'dark' | 'system'\n heightOffsetPx: number\n enableLogging?: boolean\n}\n\n/**\n * Emit function type for common events\n */\nexport interface CommonEmitFn {\n (event: 'loaded'): void\n (event: 'error', error: CapitalOSError): void\n}\n\n/**\n * Component-specific callbacks to pass to IframeManager.\n * Excludes common callbacks (onLoad, onError, onTokenExpired) which the composable handles.\n */\nexport type ComponentCallbacks = Omit<IframeManagerConfig['callbacks'], 'onLoad' | 'onError' | 'onTokenExpired'>\n\n/**\n * Options for configuring the iframe component\n */\nexport interface UseIframeComponentOptions {\n /** Props from the component */\n props: IframeComponentProps\n /** Emit function from the component */\n emit: CommonEmitFn\n /** Function that returns the rendering context for this component */\n getRenderingContext: () => RenderingContext\n /** Component-specific callbacks (onboarding, tokenExchange, etc.) */\n componentCallbacks?: ComponentCallbacks\n}\n\n/**\n * Return type for useIframeComponent\n */\nexport interface UseIframeComponentReturn {\n /** Ref to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Render function that returns the container div */\n render: () => VNode\n}\n\n/**\n * Composable that handles common iframe component setup.\n *\n * Manages:\n * - Container ref and iframe manager lifecycle\n * - Token data subscription and iframe initialization\n * - Common callbacks (onLoad, onError, onTokenExpired)\n * - Cleanup on unmount\n * - Render function for the container div\n *\n * Note: Rendering context props (e.g., entryPoint, exitPoint for Onboarding) are read once\n * at iframe initialization. Changes to these props after the iframe loads will NOT trigger\n * re-initialization. This matches the behavior of the React SDK.\n *\n * @example\n * ```ts\n * setup(props, { emit }) {\n * const { render } = useIframeComponent({\n * props,\n * emit,\n * getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n * })\n * return render\n * }\n * ```\n */\nexport function useIframeComponent(options: UseIframeComponentOptions): UseIframeComponentReturn {\n const { props, emit, getRenderingContext, componentCallbacks } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const { tokenData, config, invalidateToken } = useCapitalOsAuth()\n\n let iframeManager: IframeManager | null = null\n\n function initializeIframe(token: TokenData, container: HTMLElement): void {\n // Destroy previous iframe before creating new one (matches Angular/React behavior)\n // This ensures token refresh works correctly - new token = new iframe\n iframeManager?.destroy()\n\n const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false\n const resolvedTheme = props.theme ?? config.theme\n\n iframeManager = new IframeManager({\n container,\n tokenData: token,\n renderingContext: getRenderingContext(),\n theme: resolvedTheme,\n enableLogging: resolvedEnableLogging,\n logger: config.logger,\n sdkVersion: SDK_VERSION,\n heightOffsetPx: props.heightOffsetPx,\n callbacks: {\n onLoad: () => {\n emit('loaded')\n },\n onError: (rawError: RawErrorDetails) => {\n emit('error', new CapitalOSError(rawError))\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n ...componentCallbacks,\n },\n })\n }\n\n // Watch for tokenData and container changes to initialize the iframe.\n //\n // Why watch both tokenData AND containerRef?\n // - tokenData: comes from async token exchange, may not be ready on mount\n // - containerRef: DOM element, available after first render\n //\n // Why { immediate: true }?\n // - If tokenData is already available when component mounts, we want to\n // initialize immediately without waiting for a change\n // - Without this, the iframe wouldn't load until tokenData changes again\n watch(\n [tokenData, containerRef],\n ([newTokenData, container]) => {\n if (newTokenData && container) {\n initializeIframe(newTokenData, container)\n }\n },\n { immediate: true }\n )\n\n onUnmounted(() => {\n iframeManager?.destroy()\n iframeManager = null\n })\n\n // Render function - creates the container div for the iframe.\n //\n // Why use a function ref `ref: (el) => { ... }` instead of `ref: containerRef`?\n //\n // Vue 2.7 and Vue 3 handle template refs differently in render functions:\n // - Vue 3: can use `ref: containerRef` directly\n // - Vue 2.7: requires function ref pattern for reliable element access\n //\n // The function ref pattern works in both versions, so we use it for compatibility.\n // Do NOT change this to `ref: containerRef` - it will break Vue 2.7 support.\n //\n // The `as unknown as string` type assertion is required because:\n // - Vue 3's TypeScript types don't properly express that function refs are valid\n // - The function ref pattern IS valid at runtime in both Vue 2.7 and Vue 3\n // - This is a known limitation of Vue 3's type definitions\n function render(): VNode {\n return h('div', {\n // Type assertion required: Vue 3's types don't support function refs in h(),\n // but function refs ARE valid at runtime in both Vue 2.7 and Vue 3\n ref: ((el: Element | null) => {\n containerRef.value = el as HTMLElement | null\n }) as unknown as string,\n class: 'capitalos-iframe-container',\n style: { width: '100%' },\n })\n }\n\n return {\n containerRef,\n render,\n }\n}\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * CardsApp component that renders the CapitalOS cards interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!cardsLoaded\">Loading...</div>\n * <CardsApp\n * @loaded=\"cardsLoaded = true\"\n * @error=\"onCardsError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { CardsApp } from '@capitalos/vue'\n *\n * const cardsLoaded = ref(false)\n * const onCardsError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * BillPayApp component that renders the CapitalOS bill payment interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!billPayLoaded\">Loading...</div>\n * <BillPayApp\n * @loaded=\"billPayLoaded = true\"\n * @error=\"onBillPayError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { BillPayApp } from '@capitalos/vue'\n *\n * const billPayLoaded = ref(false)\n * const onBillPayError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const BillPayApp = defineComponent({\n name: 'BillPayApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'billPayApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport type { Account, OnboardingEntryPoint, OnboardingExitPoint } from '@capitalos/core'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * Onboarding component that renders the CapitalOS onboarding flow in an iframe.\n *\n * The component emits events for loading state and completion - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!onboardingLoaded\">Loading...</div>\n * <Onboarding\n * entry-point=\"welcome\"\n * exit-point=\"activation\"\n * @loaded=\"onboardingLoaded = true\"\n * @error=\"onOnboardingError\"\n * @done=\"onOnboardingComplete\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { Onboarding } from '@capitalos/vue'\n *\n * const onboardingLoaded = ref(false)\n * const onOnboardingError = (error) => console.error(error)\n * const onOnboardingComplete = (account) => {\n * console.log('Onboarding complete:', account.status)\n * }\n * </script>\n * ```\n */\nexport const Onboarding = defineComponent({\n name: 'Onboarding',\n props: {\n ...commonProps,\n /**\n * The starting point of the onboarding flow.\n * @default 'welcome'\n */\n entryPoint: String as PropType<OnboardingEntryPoint>,\n /**\n * The ending point of the onboarding flow.\n * @default 'activation'\n */\n exitPoint: String as PropType<OnboardingExitPoint>,\n /**\n * Whether to allow the user to exit the onboarding flow by clicking the done button\n * when they finished the onboarding form but are still not approved (e.g. waiting or pending).\n * This should only be false if you want the user to stay on the onboarding flow\n * (e.g. to add bank account) and not exit the flow.\n * @default true\n */\n allowExitOnNonApproved: {\n type: Boolean,\n default: undefined,\n },\n },\n emits: {\n ...commonEmits,\n /**\n * Emitted when the onboarding flow completes successfully.\n */\n done: (_account: Account) => true,\n },\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({\n entryPoint: 'onboarding',\n onboardingEntryPoint: props.entryPoint,\n onboardingExitPoint: props.exitPoint,\n allowExitOnNonApproved: props.allowExitOnNonApproved,\n }),\n componentCallbacks: {\n onboarding: {\n onDone: (account: Account) => {\n emit('done', account)\n },\n },\n },\n })\n\n return render\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["globalState: CapitalOsState | null","error: unknown","appOrVue: Vue2Constructor | Vue3App","config: CapitalOsConfig","logger","activeAbortController: AbortController | null","TokenType","TokenParamKey","TokenParamLocation","state: CapitalOsState","error: CapitalOSError","CapitalOSError","options: UseIframeComponentOptions","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","IframeManager","SDK_VERSION","rawError: RawErrorDetails","CapitalOSError","el: Element | null","_account: Account","account: Account"],"sources":["../src/plugin.ts","../src/components/common-props.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/composables/use-iframe-component.ts","../src/components/CardsApp.ts","../src/components/BillPayApp.ts","../src/components/Onboarding.ts"],"sourcesContent":["// =============================================================================\n// MAINTAINER NOTE: Vue 2.7 vs Vue 3 Plugin Compatibility\n// =============================================================================\n//\n// Vue 2.7 and Vue 3 call plugin.install() with DIFFERENT arguments:\n//\n// Vue 3 consumer code:\n// const app = createApp(App)\n// app.use(myPlugin) // → calls myPlugin.install(app) with app INSTANCE\n//\n// Vue 2.7 consumer code:\n// Vue.use(myPlugin) // → calls myPlugin.install(Vue) with Vue CONSTRUCTOR\n//\n// This matters because:\n// - Vue 3's app instance has app.provide() for dependency injection\n// - Vue 2.7's Vue constructor does NOT have provide() - must use Vue.mixin()\n//\n// This plugin detects which API is available and uses the appropriate method.\n// As an additional fallback for edge cases, we also store state in a module-level\n// variable that `useCapitalOsAuth` can access if `inject()` returns undefined.\n//\n// FUTURE: If we ever need to support Vue < 2.7 or have more complex version-\n// specific logic throughout the codebase, consider using `vue-demi` package\n// (https://github.com/vueuse/vue-demi) which abstracts all Vue 2/3 differences.\n// For now, our simple detection is sufficient since Vue 2.7 has Composition API\n// backported and we only need to handle the plugin install signature difference.\n// =============================================================================\n\nimport { ref, readonly } from 'vue'\nimport {\n exchangeOneTimeToken,\n createLogger,\n TokenType,\n TokenParamKey,\n TokenParamLocation,\n type TokenData,\n} from '@capitalos/core'\nimport type { CapitalOsConfig, CapitalOsState } from './types'\n\n/**\n * Injection key for CapitalOS state.\n * Used with Vue's provide/inject system.\n */\nexport const CAPITALOS_INJECTION_KEY = Symbol('capitalos')\n\n// MAINTAINER NOTE: Module-level state storage for Vue 2.7 compatibility fallback.\n//\n// In Vue 2.7, even with the mixin approach, there can be edge cases where\n// `inject()` doesn't find the provided value (e.g., in the root component\n// or components created before the mixin is applied). This global fallback\n// ensures `useCapitalOsAuth()` always has access to the state.\nlet globalState: CapitalOsState | null = null\n\n// @internal - used by useCapitalOsAuth for Vue 2.7 fallback\nexport function getGlobalState(): CapitalOsState | null {\n return globalState\n}\n\nconst TOKEN_EXCHANGE_TIMEOUT_MS = 10_000\n\n// Converts an unknown error to an Error object\nfunction toError(error: unknown): Error {\n if (error instanceof Error) {\n return error\n }\n return new Error(String(error))\n}\n\n// MAINTAINER NOTE: Module-level guard against double installation (singleton pattern).\n//\n// This MUST be module-level (not inside createCapitalOs) because:\n// - The plugin creates reactive state that should only exist once\n// - Multiple installations would create multiple token exchange iframes\n// - Multiple auth states would cause inconsistent behavior\n//\n// If a consumer accidentally calls Vue.use(createCapitalOs(...)) twice,\n// the second call will be silently ignored with a warning.\nlet installed = false\n\n// Type for Vue 2 constructor passed to plugin install.\n// In Vue 2.7, `Vue.use(plugin)` passes the Vue constructor.\ninterface Vue2Constructor {\n mixin: (mixin: Record<string, unknown>) => void\n version: string\n}\n\n// Type for Vue 3 app instance passed to plugin install.\n// In Vue 3, `app.use(plugin)` passes the app instance.\ninterface Vue3App {\n provide: (key: symbol, value: unknown) => void\n version: string\n}\n\n// Detect Vue version by checking for Vue 3's `provide` method.\n// Vue 3 app instances have `app.provide()`, Vue 2 constructors don't.\nfunction isVue3App(appOrVue: Vue2Constructor | Vue3App): appOrVue is Vue3App {\n return typeof (appOrVue as Vue3App).provide === 'function'\n}\n\n/**\n * Creates the CapitalOS Vue plugin.\n *\n * Usage (Vue 3):\n * ```typescript\n * import { createApp } from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * const app = createApp(App)\n * app.use(createCapitalOs({ getToken: async () => { ... } }))\n * app.mount('#app')\n * ```\n *\n * Usage (Vue 2.7):\n * ```typescript\n * import Vue from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * Vue.use(createCapitalOs({ getToken: async () => { ... } }))\n * new Vue({ render: h => h(App) }).$mount('#app')\n * ```\n */\nexport function createCapitalOs(config: CapitalOsConfig) {\n return {\n install(appOrVue: Vue2Constructor | Vue3App) {\n // Prevent double installation\n if (installed) {\n const logger = createLogger(config.enableLogging, config.logger)\n logger.warn('[CapitalOS] Plugin already installed, skipping duplicate installation')\n return\n }\n installed = true\n\n const logger = createLogger(config.enableLogging, config.logger)\n\n // Create reactive state\n const tokenData = ref<TokenData | undefined>(undefined)\n const isLoading = ref(false)\n const error = ref<Error | undefined>(undefined)\n\n // AbortController for cancelling in-flight token exchanges\n let activeAbortController: AbortController | null = null\n\n // Refreshes the token by fetching a new one-time token and exchanging it.\n async function refreshToken(): Promise<void> {\n // Prevent concurrent refresh calls - if already loading, skip\n if (isLoading.value) {\n logger.log('[CapitalOS] Token refresh already in progress, skipping')\n return\n }\n\n logger.log('[CapitalOS] Starting token refresh')\n isLoading.value = true\n error.value = undefined\n\n // Abort any in-flight exchange when a new one begins\n activeAbortController?.abort()\n const abortController = new AbortController()\n activeAbortController = abortController\n\n try {\n const oneTimeToken = await config.getToken()\n logger.log('[CapitalOS] Received one-time token')\n\n // Check if aborted after getToken\n if (abortController.signal.aborted) {\n return\n }\n\n // Exchange for long-lived token using @capitalos/core\n const result = await exchangeOneTimeToken({\n oneTimeToken,\n enableLogging: config.enableLogging,\n logger: config.logger,\n timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,\n signal: abortController.signal,\n })\n\n // Check if aborted after exchange\n if (abortController.signal.aborted) {\n return\n }\n\n tokenData.value = {\n token: result.longLivedToken,\n tokenType: TokenType.longLived,\n baseUrl: result.baseUrl,\n paramKey: TokenParamKey.accessToken,\n paramLocation: TokenParamLocation.hash,\n }\n logger.log('[CapitalOS] Token exchange complete')\n } catch (e) {\n // Ignore abort errors\n if (e instanceof Error && e.name === 'AbortError') {\n return\n }\n\n const err = toError(e)\n logger.error(`[CapitalOS] Token refresh failed: ${err.message}`)\n error.value = err\n } finally {\n if (activeAbortController === abortController) {\n activeAbortController = null\n }\n isLoading.value = false\n }\n }\n\n // Invalidates the current token, triggering a refresh.\n // Called when the iframe signals that the token has expired.\n function invalidateToken(): void {\n logger.log('[CapitalOS] Invalidating token')\n tokenData.value = undefined\n error.value = undefined\n refreshToken()\n }\n\n // Create state object to provide\n const state: CapitalOsState = {\n tokenData: readonly(tokenData),\n isLoading: readonly(isLoading),\n error: readonly(error),\n invalidateToken,\n config,\n }\n\n // Store globally as fallback for Vue 2.7 edge cases where inject() fails\n globalState = state\n\n // Provide state using the appropriate API based on Vue version\n if (isVue3App(appOrVue)) {\n // Vue 3: use app.provide() - this makes state available to all components\n // via inject(CAPITALOS_INJECTION_KEY)\n appOrVue.provide(CAPITALOS_INJECTION_KEY, state)\n } else {\n // Vue 2.7: use Vue.mixin() to add a provide function to all components.\n // This is the Vue 2 equivalent of app.provide() - it injects the provide\n // option into every component so inject() can find our state.\n appOrVue.mixin({\n provide() {\n return {\n [CAPITALOS_INJECTION_KEY]: state,\n }\n },\n })\n }\n\n // Start token exchange eagerly (matches Angular behavior)\n refreshToken()\n },\n }\n}\n","import type { PropType } from 'vue'\nimport type { ThemeColorScheme } from '@capitalos/core'\nimport { CapitalOSError } from '@capitalos/core'\n\n/**\n * Common props shared by all CapitalOS Vue components.\n */\nexport const commonProps = {\n /**\n * Theme color scheme for the interface.\n * Overrides the theme set in plugin configuration.\n */\n theme: String as PropType<ThemeColorScheme>,\n /**\n * Offset in pixels to add to the iframe height calculation.\n * Useful for avoiding scrollbars when the content height changes dynamically.\n */\n heightOffsetPx: {\n type: Number,\n default: 12,\n },\n /**\n * Whether to enable logging for debugging purposes.\n * Overrides the enableLogging set in plugin configuration.\n */\n enableLogging: Boolean,\n}\n\n/**\n * Common emits shared by all CapitalOS Vue components.\n */\nexport const commonEmits = {\n /**\n * Emitted when the interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n}\n","import { inject, computed, type ComputedRef } from 'vue'\nimport { CAPITALOS_INJECTION_KEY, getGlobalState } from '../plugin'\nimport type { CapitalOsState, AuthState } from '../types'\n\n/**\n * Return type for useCapitalOsAuth composable\n */\nexport interface UseCapitalOsAuthReturn extends CapitalOsState {\n /**\n * Computed authentication state\n */\n authState: ComputedRef<AuthState>\n}\n\n/**\n * @internal\n * Internal composable for accessing CapitalOS authentication state.\n * Used by SDK components - not intended for direct consumer use.\n */\nexport function useCapitalOsAuth(): UseCapitalOsAuthReturn {\n // MAINTAINER NOTE: Vue 2.7 vs Vue 3 Compatibility\n //\n // Try inject() first - this works in:\n // - Vue 3 (via app.provide)\n // - Vue 2.7 (via mixin provide, in most components)\n //\n // Fall back to getGlobalState() for Vue 2.7 edge cases where inject() fails,\n // such as the root component or components created before the mixin is applied.\n //\n // Do NOT remove the ?? getGlobalState() fallback - it's required for Vue 2.7.\n const state = inject<CapitalOsState>(CAPITALOS_INJECTION_KEY) ?? getGlobalState()\n\n if (!state) {\n throw new Error(\n 'useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. ' +\n 'Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts'\n )\n }\n\n const authState = computed<AuthState>(() => {\n if (state.isLoading.value) return 'loading'\n if (state.error.value) return 'error'\n if (state.tokenData.value) return 'authenticated'\n return 'idle'\n })\n\n return {\n ...state,\n authState,\n }\n}\n","{\n \"name\": \"@capitalos/vue\",\n \"version\": \"0.2.0\",\n \"description\": \"Vue SDK for CapitalOS\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n }\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"tsdown --watch\",\n \"lint\": \"eslint src --ext .ts --fix\",\n \"lint-ci\": \"eslint src --ext .ts\",\n \"start\": \"pnpm --filter vue-test-app dev\",\n \"start:all\": \"concurrently \\\"pnpm --filter sdk-test-server start\\\" \\\"pnpm start\\\"\",\n \"test:pack\": \"pnpm build && pnpm pack\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/CapitalOS/theboss\",\n \"directory\": \"sdk/vue\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/CapitalOS/theboss/issues\"\n },\n \"author\": \"CapitalOS\",\n \"license\": \"MIT\",\n \"peerDependencies\": {\n \"vue\": \"^2.7.0 || ^3.0.0\"\n },\n \"dependencies\": {\n \"@capitalos/core\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"tsdown\": \"^0.9.2\",\n \"typescript\": \"^5.6.2\",\n \"vue\": \"^3.5.13\",\n \"concurrently\": \"^9.1.2\"\n }\n}\n","import { ref, watch, onUnmounted, h, type VNode, type Ref } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type TokenData,\n type RawErrorDetails,\n type RenderingContext,\n type IframeManagerConfig,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from './use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\n\n/**\n * Props expected by useIframeComponent - matches commonProps shape\n */\nexport interface IframeComponentProps {\n theme?: 'light' | 'dark' | 'system'\n heightOffsetPx: number\n enableLogging?: boolean\n}\n\n/**\n * Emit function type for common events\n */\nexport interface CommonEmitFn {\n (event: 'loaded'): void\n (event: 'error', error: CapitalOSError): void\n}\n\n/**\n * Component-specific callbacks to pass to IframeManager.\n * Excludes common callbacks (onLoad, onError, onTokenExpired) which the composable handles.\n */\nexport type ComponentCallbacks = Omit<IframeManagerConfig['callbacks'], 'onLoad' | 'onError' | 'onTokenExpired'>\n\n/**\n * Options for configuring the iframe component\n */\nexport interface UseIframeComponentOptions {\n /** Props from the component */\n props: IframeComponentProps\n /** Emit function from the component */\n emit: CommonEmitFn\n /** Function that returns the rendering context for this component */\n getRenderingContext: () => RenderingContext\n /** Component-specific callbacks (onboarding, tokenExchange, etc.) */\n componentCallbacks?: ComponentCallbacks\n}\n\n/**\n * Return type for useIframeComponent\n */\nexport interface UseIframeComponentReturn {\n /** Ref to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Render function that returns the container div */\n render: () => VNode\n}\n\n/**\n * Composable that handles common iframe component setup.\n *\n * Manages:\n * - Container ref and iframe manager lifecycle\n * - Token data subscription and iframe initialization\n * - Common callbacks (onLoad, onError, onTokenExpired)\n * - Cleanup on unmount\n * - Render function for the container div\n *\n * Note: Rendering context props (e.g., entryPoint, exitPoint for Onboarding) are read once\n * at iframe initialization. Changes to these props after the iframe loads will NOT trigger\n * re-initialization. This matches the behavior of the React SDK.\n *\n * @example\n * ```ts\n * setup(props, { emit }) {\n * const { render } = useIframeComponent({\n * props,\n * emit,\n * getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n * })\n * return render\n * }\n * ```\n */\nexport function useIframeComponent(options: UseIframeComponentOptions): UseIframeComponentReturn {\n const { props, emit, getRenderingContext, componentCallbacks } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const { tokenData, config, invalidateToken } = useCapitalOsAuth()\n\n let iframeManager: IframeManager | null = null\n\n function initializeIframe(token: TokenData, container: HTMLElement): void {\n // Destroy previous iframe before creating new one (matches Angular/React behavior)\n // This ensures token refresh works correctly - new token = new iframe\n iframeManager?.destroy()\n\n const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false\n const resolvedTheme = props.theme ?? config.theme\n\n iframeManager = new IframeManager({\n container,\n tokenData: token,\n renderingContext: getRenderingContext(),\n theme: resolvedTheme,\n enableLogging: resolvedEnableLogging,\n logger: config.logger,\n sdkVersion: SDK_VERSION,\n heightOffsetPx: props.heightOffsetPx,\n callbacks: {\n onLoad: () => {\n emit('loaded')\n },\n onError: (rawError: RawErrorDetails) => {\n emit('error', new CapitalOSError(rawError))\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n ...componentCallbacks,\n },\n })\n }\n\n // Watch for tokenData and container changes to initialize the iframe.\n //\n // Why watch both tokenData AND containerRef?\n // - tokenData: comes from async token exchange, may not be ready on mount\n // - containerRef: DOM element, available after first render\n //\n // Why { immediate: true }?\n // - If tokenData is already available when component mounts, we want to\n // initialize immediately without waiting for a change\n // - Without this, the iframe wouldn't load until tokenData changes again\n watch(\n [tokenData, containerRef],\n ([newTokenData, container]) => {\n if (newTokenData && container) {\n initializeIframe(newTokenData, container)\n }\n },\n { immediate: true }\n )\n\n onUnmounted(() => {\n iframeManager?.destroy()\n iframeManager = null\n })\n\n // Render function - creates the container div for the iframe.\n //\n // Why use a function ref `ref: (el) => { ... }` instead of `ref: containerRef`?\n //\n // Vue 2.7 and Vue 3 handle template refs differently in render functions:\n // - Vue 3: can use `ref: containerRef` directly\n // - Vue 2.7: requires function ref pattern for reliable element access\n //\n // The function ref pattern works in both versions, so we use it for compatibility.\n // Do NOT change this to `ref: containerRef` - it will break Vue 2.7 support.\n //\n // The `as unknown as string` type assertion is required because:\n // - Vue 3's TypeScript types don't properly express that function refs are valid\n // - The function ref pattern IS valid at runtime in both Vue 2.7 and Vue 3\n // - This is a known limitation of Vue 3's type definitions\n function render(): VNode {\n return h('div', {\n // Type assertion required: Vue 3's types don't support function refs in h(),\n // but function refs ARE valid at runtime in both Vue 2.7 and Vue 3\n ref: ((el: Element | null) => {\n containerRef.value = el as HTMLElement | null\n }) as unknown as string,\n class: 'capitalos-iframe-container',\n style: { width: '100%' },\n })\n }\n\n return {\n containerRef,\n render,\n }\n}\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * CardsApp component that renders the CapitalOS cards interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!cardsLoaded\">Loading...</div>\n * <CardsApp\n * @loaded=\"cardsLoaded = true\"\n * @error=\"onCardsError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { CardsApp } from '@capitalos/vue'\n *\n * const cardsLoaded = ref(false)\n * const onCardsError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * BillPayApp component that renders the CapitalOS bill payment interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!billPayLoaded\">Loading...</div>\n * <BillPayApp\n * @loaded=\"billPayLoaded = true\"\n * @error=\"onBillPayError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { BillPayApp } from '@capitalos/vue'\n *\n * const billPayLoaded = ref(false)\n * const onBillPayError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const BillPayApp = defineComponent({\n name: 'BillPayApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'billPayApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport type { Account, CopyOverrides, OnboardingEntryPoint, OnboardingExitPoint } from '@capitalos/core'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * Onboarding component that renders the CapitalOS onboarding flow in an iframe.\n *\n * The component emits events for loading state and completion - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!onboardingLoaded\">Loading...</div>\n * <Onboarding\n * entry-point=\"welcome\"\n * exit-point=\"activation\"\n * @loaded=\"onboardingLoaded = true\"\n * @error=\"onOnboardingError\"\n * @done=\"onOnboardingComplete\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { Onboarding } from '@capitalos/vue'\n *\n * const onboardingLoaded = ref(false)\n * const onOnboardingError = (error) => console.error(error)\n * const onOnboardingComplete = (account) => {\n * console.log('Onboarding complete:', account.status)\n * }\n * </script>\n * ```\n */\nexport const Onboarding = defineComponent({\n name: 'Onboarding',\n props: {\n ...commonProps,\n /**\n * The starting point of the onboarding flow.\n * @default 'welcome'\n */\n entryPoint: String as PropType<OnboardingEntryPoint>,\n /**\n * The ending point of the onboarding flow.\n * @default 'activation'\n */\n exitPoint: String as PropType<OnboardingExitPoint>,\n /**\n * Whether to allow the user to exit the onboarding flow by clicking the done button\n * when they finished the onboarding form but are still not approved (e.g. waiting or pending).\n * This should only be false if you want the user to stay on the onboarding flow\n * (e.g. to add bank account) and not exit the flow.\n * @default true\n */\n allowExitOnNonApproved: {\n type: Boolean,\n default: undefined,\n },\n /**\n * Custom text for the final \"done\" button shown at the end of the onboarding flow.\n * If not provided, the default text for each exit screen is used.\n */\n doneButtonText: String as PropType<string>,\n },\n emits: {\n ...commonEmits,\n /**\n * Emitted when the onboarding flow completes successfully.\n */\n done: (_account: Account) => true,\n },\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({\n entryPoint: 'onboarding',\n onboardingEntryPoint: props.entryPoint,\n onboardingExitPoint: props.exitPoint,\n allowExitOnNonApproved: props.allowExitOnNonApproved,\n copy: props.doneButtonText\n ? ({\n 'onboarding.doneButton': props.doneButtonText,\n 'onboarding.continueButton': props.doneButtonText,\n 'activation.successButton': props.doneButtonText,\n } satisfies CopyOverrides)\n : undefined,\n }),\n componentCallbacks: {\n onboarding: {\n onDone: (account: Account) => {\n emit('done', account)\n },\n },\n },\n })\n\n return render\n },\n})\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -163,7 +163,7 @@ function useCapitalOsAuth() {
|
|
|
163
163
|
|
|
164
164
|
//#endregion
|
|
165
165
|
//#region package.json
|
|
166
|
-
var version = "0.
|
|
166
|
+
var version = "0.2.0";
|
|
167
167
|
|
|
168
168
|
//#endregion
|
|
169
169
|
//#region src/composables/use-iframe-component.ts
|
|
@@ -368,7 +368,8 @@ const Onboarding = defineComponent({
|
|
|
368
368
|
allowExitOnNonApproved: {
|
|
369
369
|
type: Boolean,
|
|
370
370
|
default: void 0
|
|
371
|
-
}
|
|
371
|
+
},
|
|
372
|
+
doneButtonText: String
|
|
372
373
|
},
|
|
373
374
|
emits: {
|
|
374
375
|
...commonEmits,
|
|
@@ -382,7 +383,12 @@ const Onboarding = defineComponent({
|
|
|
382
383
|
entryPoint: "onboarding",
|
|
383
384
|
onboardingEntryPoint: props.entryPoint,
|
|
384
385
|
onboardingExitPoint: props.exitPoint,
|
|
385
|
-
allowExitOnNonApproved: props.allowExitOnNonApproved
|
|
386
|
+
allowExitOnNonApproved: props.allowExitOnNonApproved,
|
|
387
|
+
copy: props.doneButtonText ? {
|
|
388
|
+
"onboarding.doneButton": props.doneButtonText,
|
|
389
|
+
"onboarding.continueButton": props.doneButtonText,
|
|
390
|
+
"activation.successButton": props.doneButtonText
|
|
391
|
+
} : void 0
|
|
386
392
|
}),
|
|
387
393
|
componentCallbacks: { onboarding: { onDone: (account) => {
|
|
388
394
|
emit("done", account);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["globalState: CapitalOsState | null","error: unknown","appOrVue: Vue2Constructor | Vue3App","config: CapitalOsConfig","logger","activeAbortController: AbortController | null","state: CapitalOsState","error: CapitalOSError","CapitalOSError","options: UseIframeComponentOptions","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","SDK_VERSION","rawError: RawErrorDetails","CapitalOSError","el: Element | null","_account: Account","account: Account"],"sources":["../src/plugin.ts","../src/components/common-props.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/composables/use-iframe-component.ts","../src/components/CardsApp.ts","../src/components/BillPayApp.ts","../src/components/Onboarding.ts"],"sourcesContent":["// =============================================================================\n// MAINTAINER NOTE: Vue 2.7 vs Vue 3 Plugin Compatibility\n// =============================================================================\n//\n// Vue 2.7 and Vue 3 call plugin.install() with DIFFERENT arguments:\n//\n// Vue 3 consumer code:\n// const app = createApp(App)\n// app.use(myPlugin) // → calls myPlugin.install(app) with app INSTANCE\n//\n// Vue 2.7 consumer code:\n// Vue.use(myPlugin) // → calls myPlugin.install(Vue) with Vue CONSTRUCTOR\n//\n// This matters because:\n// - Vue 3's app instance has app.provide() for dependency injection\n// - Vue 2.7's Vue constructor does NOT have provide() - must use Vue.mixin()\n//\n// This plugin detects which API is available and uses the appropriate method.\n// As an additional fallback for edge cases, we also store state in a module-level\n// variable that `useCapitalOsAuth` can access if `inject()` returns undefined.\n//\n// FUTURE: If we ever need to support Vue < 2.7 or have more complex version-\n// specific logic throughout the codebase, consider using `vue-demi` package\n// (https://github.com/vueuse/vue-demi) which abstracts all Vue 2/3 differences.\n// For now, our simple detection is sufficient since Vue 2.7 has Composition API\n// backported and we only need to handle the plugin install signature difference.\n// =============================================================================\n\nimport { ref, readonly } from 'vue'\nimport {\n exchangeOneTimeToken,\n createLogger,\n TokenType,\n TokenParamKey,\n TokenParamLocation,\n type TokenData,\n} from '@capitalos/core'\nimport type { CapitalOsConfig, CapitalOsState } from './types'\n\n/**\n * Injection key for CapitalOS state.\n * Used with Vue's provide/inject system.\n */\nexport const CAPITALOS_INJECTION_KEY = Symbol('capitalos')\n\n// MAINTAINER NOTE: Module-level state storage for Vue 2.7 compatibility fallback.\n//\n// In Vue 2.7, even with the mixin approach, there can be edge cases where\n// `inject()` doesn't find the provided value (e.g., in the root component\n// or components created before the mixin is applied). This global fallback\n// ensures `useCapitalOsAuth()` always has access to the state.\nlet globalState: CapitalOsState | null = null\n\n// @internal - used by useCapitalOsAuth for Vue 2.7 fallback\nexport function getGlobalState(): CapitalOsState | null {\n return globalState\n}\n\nconst TOKEN_EXCHANGE_TIMEOUT_MS = 10_000\n\n// Converts an unknown error to an Error object\nfunction toError(error: unknown): Error {\n if (error instanceof Error) {\n return error\n }\n return new Error(String(error))\n}\n\n// MAINTAINER NOTE: Module-level guard against double installation (singleton pattern).\n//\n// This MUST be module-level (not inside createCapitalOs) because:\n// - The plugin creates reactive state that should only exist once\n// - Multiple installations would create multiple token exchange iframes\n// - Multiple auth states would cause inconsistent behavior\n//\n// If a consumer accidentally calls Vue.use(createCapitalOs(...)) twice,\n// the second call will be silently ignored with a warning.\nlet installed = false\n\n// Type for Vue 2 constructor passed to plugin install.\n// In Vue 2.7, `Vue.use(plugin)` passes the Vue constructor.\ninterface Vue2Constructor {\n mixin: (mixin: Record<string, unknown>) => void\n version: string\n}\n\n// Type for Vue 3 app instance passed to plugin install.\n// In Vue 3, `app.use(plugin)` passes the app instance.\ninterface Vue3App {\n provide: (key: symbol, value: unknown) => void\n version: string\n}\n\n// Detect Vue version by checking for Vue 3's `provide` method.\n// Vue 3 app instances have `app.provide()`, Vue 2 constructors don't.\nfunction isVue3App(appOrVue: Vue2Constructor | Vue3App): appOrVue is Vue3App {\n return typeof (appOrVue as Vue3App).provide === 'function'\n}\n\n/**\n * Creates the CapitalOS Vue plugin.\n *\n * Usage (Vue 3):\n * ```typescript\n * import { createApp } from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * const app = createApp(App)\n * app.use(createCapitalOs({ getToken: async () => { ... } }))\n * app.mount('#app')\n * ```\n *\n * Usage (Vue 2.7):\n * ```typescript\n * import Vue from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * Vue.use(createCapitalOs({ getToken: async () => { ... } }))\n * new Vue({ render: h => h(App) }).$mount('#app')\n * ```\n */\nexport function createCapitalOs(config: CapitalOsConfig) {\n return {\n install(appOrVue: Vue2Constructor | Vue3App) {\n // Prevent double installation\n if (installed) {\n const logger = createLogger(config.enableLogging, config.logger)\n logger.warn('[CapitalOS] Plugin already installed, skipping duplicate installation')\n return\n }\n installed = true\n\n const logger = createLogger(config.enableLogging, config.logger)\n\n // Create reactive state\n const tokenData = ref<TokenData | undefined>(undefined)\n const isLoading = ref(false)\n const error = ref<Error | undefined>(undefined)\n\n // AbortController for cancelling in-flight token exchanges\n let activeAbortController: AbortController | null = null\n\n // Refreshes the token by fetching a new one-time token and exchanging it.\n async function refreshToken(): Promise<void> {\n // Prevent concurrent refresh calls - if already loading, skip\n if (isLoading.value) {\n logger.log('[CapitalOS] Token refresh already in progress, skipping')\n return\n }\n\n logger.log('[CapitalOS] Starting token refresh')\n isLoading.value = true\n error.value = undefined\n\n // Abort any in-flight exchange when a new one begins\n activeAbortController?.abort()\n const abortController = new AbortController()\n activeAbortController = abortController\n\n try {\n const oneTimeToken = await config.getToken()\n logger.log('[CapitalOS] Received one-time token')\n\n // Check if aborted after getToken\n if (abortController.signal.aborted) {\n return\n }\n\n // Exchange for long-lived token using @capitalos/core\n const result = await exchangeOneTimeToken({\n oneTimeToken,\n enableLogging: config.enableLogging,\n logger: config.logger,\n timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,\n signal: abortController.signal,\n })\n\n // Check if aborted after exchange\n if (abortController.signal.aborted) {\n return\n }\n\n tokenData.value = {\n token: result.longLivedToken,\n tokenType: TokenType.longLived,\n baseUrl: result.baseUrl,\n paramKey: TokenParamKey.accessToken,\n paramLocation: TokenParamLocation.hash,\n }\n logger.log('[CapitalOS] Token exchange complete')\n } catch (e) {\n // Ignore abort errors\n if (e instanceof Error && e.name === 'AbortError') {\n return\n }\n\n const err = toError(e)\n logger.error(`[CapitalOS] Token refresh failed: ${err.message}`)\n error.value = err\n } finally {\n if (activeAbortController === abortController) {\n activeAbortController = null\n }\n isLoading.value = false\n }\n }\n\n // Invalidates the current token, triggering a refresh.\n // Called when the iframe signals that the token has expired.\n function invalidateToken(): void {\n logger.log('[CapitalOS] Invalidating token')\n tokenData.value = undefined\n error.value = undefined\n refreshToken()\n }\n\n // Create state object to provide\n const state: CapitalOsState = {\n tokenData: readonly(tokenData),\n isLoading: readonly(isLoading),\n error: readonly(error),\n invalidateToken,\n config,\n }\n\n // Store globally as fallback for Vue 2.7 edge cases where inject() fails\n globalState = state\n\n // Provide state using the appropriate API based on Vue version\n if (isVue3App(appOrVue)) {\n // Vue 3: use app.provide() - this makes state available to all components\n // via inject(CAPITALOS_INJECTION_KEY)\n appOrVue.provide(CAPITALOS_INJECTION_KEY, state)\n } else {\n // Vue 2.7: use Vue.mixin() to add a provide function to all components.\n // This is the Vue 2 equivalent of app.provide() - it injects the provide\n // option into every component so inject() can find our state.\n appOrVue.mixin({\n provide() {\n return {\n [CAPITALOS_INJECTION_KEY]: state,\n }\n },\n })\n }\n\n // Start token exchange eagerly (matches Angular behavior)\n refreshToken()\n },\n }\n}\n","import type { PropType } from 'vue'\nimport type { ThemeColorScheme } from '@capitalos/core'\nimport { CapitalOSError } from '@capitalos/core'\n\n/**\n * Common props shared by all CapitalOS Vue components.\n */\nexport const commonProps = {\n /**\n * Theme color scheme for the interface.\n * Overrides the theme set in plugin configuration.\n */\n theme: String as PropType<ThemeColorScheme>,\n /**\n * Offset in pixels to add to the iframe height calculation.\n * Useful for avoiding scrollbars when the content height changes dynamically.\n */\n heightOffsetPx: {\n type: Number,\n default: 12,\n },\n /**\n * Whether to enable logging for debugging purposes.\n * Overrides the enableLogging set in plugin configuration.\n */\n enableLogging: Boolean,\n}\n\n/**\n * Common emits shared by all CapitalOS Vue components.\n */\nexport const commonEmits = {\n /**\n * Emitted when the interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n}\n","import { inject, computed, type ComputedRef } from 'vue'\nimport { CAPITALOS_INJECTION_KEY, getGlobalState } from '../plugin'\nimport type { CapitalOsState, AuthState } from '../types'\n\n/**\n * Return type for useCapitalOsAuth composable\n */\nexport interface UseCapitalOsAuthReturn extends CapitalOsState {\n /**\n * Computed authentication state\n */\n authState: ComputedRef<AuthState>\n}\n\n/**\n * @internal\n * Internal composable for accessing CapitalOS authentication state.\n * Used by SDK components - not intended for direct consumer use.\n */\nexport function useCapitalOsAuth(): UseCapitalOsAuthReturn {\n // MAINTAINER NOTE: Vue 2.7 vs Vue 3 Compatibility\n //\n // Try inject() first - this works in:\n // - Vue 3 (via app.provide)\n // - Vue 2.7 (via mixin provide, in most components)\n //\n // Fall back to getGlobalState() for Vue 2.7 edge cases where inject() fails,\n // such as the root component or components created before the mixin is applied.\n //\n // Do NOT remove the ?? getGlobalState() fallback - it's required for Vue 2.7.\n const state = inject<CapitalOsState>(CAPITALOS_INJECTION_KEY) ?? getGlobalState()\n\n if (!state) {\n throw new Error(\n 'useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. ' +\n 'Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts'\n )\n }\n\n const authState = computed<AuthState>(() => {\n if (state.isLoading.value) return 'loading'\n if (state.error.value) return 'error'\n if (state.tokenData.value) return 'authenticated'\n return 'idle'\n })\n\n return {\n ...state,\n authState,\n }\n}\n","{\n \"name\": \"@capitalos/vue\",\n \"version\": \"0.1.0\",\n \"description\": \"Vue SDK for CapitalOS\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n }\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"tsdown --watch\",\n \"lint\": \"eslint src --ext .ts --fix\",\n \"lint-ci\": \"eslint src --ext .ts\",\n \"start\": \"pnpm --filter vue-test-app dev\",\n \"start:all\": \"concurrently \\\"pnpm --filter sdk-test-server start\\\" \\\"pnpm start\\\"\",\n \"test:pack\": \"pnpm build && pnpm pack\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/CapitalOS/theboss\",\n \"directory\": \"sdk/vue\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/CapitalOS/theboss/issues\"\n },\n \"author\": \"CapitalOS\",\n \"license\": \"MIT\",\n \"peerDependencies\": {\n \"vue\": \"^2.7.0 || ^3.0.0\"\n },\n \"dependencies\": {\n \"@capitalos/core\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"tsdown\": \"^0.9.2\",\n \"typescript\": \"^5.6.2\",\n \"vue\": \"^3.5.13\",\n \"concurrently\": \"^9.1.2\"\n }\n}\n","import { ref, watch, onUnmounted, h, type VNode, type Ref } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type TokenData,\n type RawErrorDetails,\n type RenderingContext,\n type IframeManagerConfig,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from './use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\n\n/**\n * Props expected by useIframeComponent - matches commonProps shape\n */\nexport interface IframeComponentProps {\n theme?: 'light' | 'dark' | 'system'\n heightOffsetPx: number\n enableLogging?: boolean\n}\n\n/**\n * Emit function type for common events\n */\nexport interface CommonEmitFn {\n (event: 'loaded'): void\n (event: 'error', error: CapitalOSError): void\n}\n\n/**\n * Component-specific callbacks to pass to IframeManager.\n * Excludes common callbacks (onLoad, onError, onTokenExpired) which the composable handles.\n */\nexport type ComponentCallbacks = Omit<IframeManagerConfig['callbacks'], 'onLoad' | 'onError' | 'onTokenExpired'>\n\n/**\n * Options for configuring the iframe component\n */\nexport interface UseIframeComponentOptions {\n /** Props from the component */\n props: IframeComponentProps\n /** Emit function from the component */\n emit: CommonEmitFn\n /** Function that returns the rendering context for this component */\n getRenderingContext: () => RenderingContext\n /** Component-specific callbacks (onboarding, tokenExchange, etc.) */\n componentCallbacks?: ComponentCallbacks\n}\n\n/**\n * Return type for useIframeComponent\n */\nexport interface UseIframeComponentReturn {\n /** Ref to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Render function that returns the container div */\n render: () => VNode\n}\n\n/**\n * Composable that handles common iframe component setup.\n *\n * Manages:\n * - Container ref and iframe manager lifecycle\n * - Token data subscription and iframe initialization\n * - Common callbacks (onLoad, onError, onTokenExpired)\n * - Cleanup on unmount\n * - Render function for the container div\n *\n * Note: Rendering context props (e.g., entryPoint, exitPoint for Onboarding) are read once\n * at iframe initialization. Changes to these props after the iframe loads will NOT trigger\n * re-initialization. This matches the behavior of the React SDK.\n *\n * @example\n * ```ts\n * setup(props, { emit }) {\n * const { render } = useIframeComponent({\n * props,\n * emit,\n * getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n * })\n * return render\n * }\n * ```\n */\nexport function useIframeComponent(options: UseIframeComponentOptions): UseIframeComponentReturn {\n const { props, emit, getRenderingContext, componentCallbacks } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const { tokenData, config, invalidateToken } = useCapitalOsAuth()\n\n let iframeManager: IframeManager | null = null\n\n function initializeIframe(token: TokenData, container: HTMLElement): void {\n // Destroy previous iframe before creating new one (matches Angular/React behavior)\n // This ensures token refresh works correctly - new token = new iframe\n iframeManager?.destroy()\n\n const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false\n const resolvedTheme = props.theme ?? config.theme\n\n iframeManager = new IframeManager({\n container,\n tokenData: token,\n renderingContext: getRenderingContext(),\n theme: resolvedTheme,\n enableLogging: resolvedEnableLogging,\n logger: config.logger,\n sdkVersion: SDK_VERSION,\n heightOffsetPx: props.heightOffsetPx,\n callbacks: {\n onLoad: () => {\n emit('loaded')\n },\n onError: (rawError: RawErrorDetails) => {\n emit('error', new CapitalOSError(rawError))\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n ...componentCallbacks,\n },\n })\n }\n\n // Watch for tokenData and container changes to initialize the iframe.\n //\n // Why watch both tokenData AND containerRef?\n // - tokenData: comes from async token exchange, may not be ready on mount\n // - containerRef: DOM element, available after first render\n //\n // Why { immediate: true }?\n // - If tokenData is already available when component mounts, we want to\n // initialize immediately without waiting for a change\n // - Without this, the iframe wouldn't load until tokenData changes again\n watch(\n [tokenData, containerRef],\n ([newTokenData, container]) => {\n if (newTokenData && container) {\n initializeIframe(newTokenData, container)\n }\n },\n { immediate: true }\n )\n\n onUnmounted(() => {\n iframeManager?.destroy()\n iframeManager = null\n })\n\n // Render function - creates the container div for the iframe.\n //\n // Why use a function ref `ref: (el) => { ... }` instead of `ref: containerRef`?\n //\n // Vue 2.7 and Vue 3 handle template refs differently in render functions:\n // - Vue 3: can use `ref: containerRef` directly\n // - Vue 2.7: requires function ref pattern for reliable element access\n //\n // The function ref pattern works in both versions, so we use it for compatibility.\n // Do NOT change this to `ref: containerRef` - it will break Vue 2.7 support.\n //\n // The `as unknown as string` type assertion is required because:\n // - Vue 3's TypeScript types don't properly express that function refs are valid\n // - The function ref pattern IS valid at runtime in both Vue 2.7 and Vue 3\n // - This is a known limitation of Vue 3's type definitions\n function render(): VNode {\n return h('div', {\n // Type assertion required: Vue 3's types don't support function refs in h(),\n // but function refs ARE valid at runtime in both Vue 2.7 and Vue 3\n ref: ((el: Element | null) => {\n containerRef.value = el as HTMLElement | null\n }) as unknown as string,\n class: 'capitalos-iframe-container',\n style: { width: '100%' },\n })\n }\n\n return {\n containerRef,\n render,\n }\n}\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * CardsApp component that renders the CapitalOS cards interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!cardsLoaded\">Loading...</div>\n * <CardsApp\n * @loaded=\"cardsLoaded = true\"\n * @error=\"onCardsError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { CardsApp } from '@capitalos/vue'\n *\n * const cardsLoaded = ref(false)\n * const onCardsError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * BillPayApp component that renders the CapitalOS bill payment interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!billPayLoaded\">Loading...</div>\n * <BillPayApp\n * @loaded=\"billPayLoaded = true\"\n * @error=\"onBillPayError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { BillPayApp } from '@capitalos/vue'\n *\n * const billPayLoaded = ref(false)\n * const onBillPayError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const BillPayApp = defineComponent({\n name: 'BillPayApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'billPayApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport type { Account, OnboardingEntryPoint, OnboardingExitPoint } from '@capitalos/core'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * Onboarding component that renders the CapitalOS onboarding flow in an iframe.\n *\n * The component emits events for loading state and completion - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!onboardingLoaded\">Loading...</div>\n * <Onboarding\n * entry-point=\"welcome\"\n * exit-point=\"activation\"\n * @loaded=\"onboardingLoaded = true\"\n * @error=\"onOnboardingError\"\n * @done=\"onOnboardingComplete\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { Onboarding } from '@capitalos/vue'\n *\n * const onboardingLoaded = ref(false)\n * const onOnboardingError = (error) => console.error(error)\n * const onOnboardingComplete = (account) => {\n * console.log('Onboarding complete:', account.status)\n * }\n * </script>\n * ```\n */\nexport const Onboarding = defineComponent({\n name: 'Onboarding',\n props: {\n ...commonProps,\n /**\n * The starting point of the onboarding flow.\n * @default 'welcome'\n */\n entryPoint: String as PropType<OnboardingEntryPoint>,\n /**\n * The ending point of the onboarding flow.\n * @default 'activation'\n */\n exitPoint: String as PropType<OnboardingExitPoint>,\n /**\n * Whether to allow the user to exit the onboarding flow by clicking the done button\n * when they finished the onboarding form but are still not approved (e.g. waiting or pending).\n * This should only be false if you want the user to stay on the onboarding flow\n * (e.g. to add bank account) and not exit the flow.\n * @default true\n */\n allowExitOnNonApproved: {\n type: Boolean,\n default: undefined,\n },\n },\n emits: {\n ...commonEmits,\n /**\n * Emitted when the onboarding flow completes successfully.\n */\n done: (_account: Account) => true,\n },\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({\n entryPoint: 'onboarding',\n onboardingEntryPoint: props.entryPoint,\n onboardingExitPoint: props.exitPoint,\n allowExitOnNonApproved: props.allowExitOnNonApproved,\n }),\n componentCallbacks: {\n onboarding: {\n onDone: (account: Account) => {\n emit('done', account)\n },\n },\n },\n })\n\n return render\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["globalState: CapitalOsState | null","error: unknown","appOrVue: Vue2Constructor | Vue3App","config: CapitalOsConfig","logger","activeAbortController: AbortController | null","state: CapitalOsState","error: CapitalOSError","CapitalOSError","options: UseIframeComponentOptions","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","SDK_VERSION","rawError: RawErrorDetails","CapitalOSError","el: Element | null","_account: Account","account: Account"],"sources":["../src/plugin.ts","../src/components/common-props.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/composables/use-iframe-component.ts","../src/components/CardsApp.ts","../src/components/BillPayApp.ts","../src/components/Onboarding.ts"],"sourcesContent":["// =============================================================================\n// MAINTAINER NOTE: Vue 2.7 vs Vue 3 Plugin Compatibility\n// =============================================================================\n//\n// Vue 2.7 and Vue 3 call plugin.install() with DIFFERENT arguments:\n//\n// Vue 3 consumer code:\n// const app = createApp(App)\n// app.use(myPlugin) // → calls myPlugin.install(app) with app INSTANCE\n//\n// Vue 2.7 consumer code:\n// Vue.use(myPlugin) // → calls myPlugin.install(Vue) with Vue CONSTRUCTOR\n//\n// This matters because:\n// - Vue 3's app instance has app.provide() for dependency injection\n// - Vue 2.7's Vue constructor does NOT have provide() - must use Vue.mixin()\n//\n// This plugin detects which API is available and uses the appropriate method.\n// As an additional fallback for edge cases, we also store state in a module-level\n// variable that `useCapitalOsAuth` can access if `inject()` returns undefined.\n//\n// FUTURE: If we ever need to support Vue < 2.7 or have more complex version-\n// specific logic throughout the codebase, consider using `vue-demi` package\n// (https://github.com/vueuse/vue-demi) which abstracts all Vue 2/3 differences.\n// For now, our simple detection is sufficient since Vue 2.7 has Composition API\n// backported and we only need to handle the plugin install signature difference.\n// =============================================================================\n\nimport { ref, readonly } from 'vue'\nimport {\n exchangeOneTimeToken,\n createLogger,\n TokenType,\n TokenParamKey,\n TokenParamLocation,\n type TokenData,\n} from '@capitalos/core'\nimport type { CapitalOsConfig, CapitalOsState } from './types'\n\n/**\n * Injection key for CapitalOS state.\n * Used with Vue's provide/inject system.\n */\nexport const CAPITALOS_INJECTION_KEY = Symbol('capitalos')\n\n// MAINTAINER NOTE: Module-level state storage for Vue 2.7 compatibility fallback.\n//\n// In Vue 2.7, even with the mixin approach, there can be edge cases where\n// `inject()` doesn't find the provided value (e.g., in the root component\n// or components created before the mixin is applied). This global fallback\n// ensures `useCapitalOsAuth()` always has access to the state.\nlet globalState: CapitalOsState | null = null\n\n// @internal - used by useCapitalOsAuth for Vue 2.7 fallback\nexport function getGlobalState(): CapitalOsState | null {\n return globalState\n}\n\nconst TOKEN_EXCHANGE_TIMEOUT_MS = 10_000\n\n// Converts an unknown error to an Error object\nfunction toError(error: unknown): Error {\n if (error instanceof Error) {\n return error\n }\n return new Error(String(error))\n}\n\n// MAINTAINER NOTE: Module-level guard against double installation (singleton pattern).\n//\n// This MUST be module-level (not inside createCapitalOs) because:\n// - The plugin creates reactive state that should only exist once\n// - Multiple installations would create multiple token exchange iframes\n// - Multiple auth states would cause inconsistent behavior\n//\n// If a consumer accidentally calls Vue.use(createCapitalOs(...)) twice,\n// the second call will be silently ignored with a warning.\nlet installed = false\n\n// Type for Vue 2 constructor passed to plugin install.\n// In Vue 2.7, `Vue.use(plugin)` passes the Vue constructor.\ninterface Vue2Constructor {\n mixin: (mixin: Record<string, unknown>) => void\n version: string\n}\n\n// Type for Vue 3 app instance passed to plugin install.\n// In Vue 3, `app.use(plugin)` passes the app instance.\ninterface Vue3App {\n provide: (key: symbol, value: unknown) => void\n version: string\n}\n\n// Detect Vue version by checking for Vue 3's `provide` method.\n// Vue 3 app instances have `app.provide()`, Vue 2 constructors don't.\nfunction isVue3App(appOrVue: Vue2Constructor | Vue3App): appOrVue is Vue3App {\n return typeof (appOrVue as Vue3App).provide === 'function'\n}\n\n/**\n * Creates the CapitalOS Vue plugin.\n *\n * Usage (Vue 3):\n * ```typescript\n * import { createApp } from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * const app = createApp(App)\n * app.use(createCapitalOs({ getToken: async () => { ... } }))\n * app.mount('#app')\n * ```\n *\n * Usage (Vue 2.7):\n * ```typescript\n * import Vue from 'vue'\n * import { createCapitalOs } from '@capitalos/vue'\n *\n * Vue.use(createCapitalOs({ getToken: async () => { ... } }))\n * new Vue({ render: h => h(App) }).$mount('#app')\n * ```\n */\nexport function createCapitalOs(config: CapitalOsConfig) {\n return {\n install(appOrVue: Vue2Constructor | Vue3App) {\n // Prevent double installation\n if (installed) {\n const logger = createLogger(config.enableLogging, config.logger)\n logger.warn('[CapitalOS] Plugin already installed, skipping duplicate installation')\n return\n }\n installed = true\n\n const logger = createLogger(config.enableLogging, config.logger)\n\n // Create reactive state\n const tokenData = ref<TokenData | undefined>(undefined)\n const isLoading = ref(false)\n const error = ref<Error | undefined>(undefined)\n\n // AbortController for cancelling in-flight token exchanges\n let activeAbortController: AbortController | null = null\n\n // Refreshes the token by fetching a new one-time token and exchanging it.\n async function refreshToken(): Promise<void> {\n // Prevent concurrent refresh calls - if already loading, skip\n if (isLoading.value) {\n logger.log('[CapitalOS] Token refresh already in progress, skipping')\n return\n }\n\n logger.log('[CapitalOS] Starting token refresh')\n isLoading.value = true\n error.value = undefined\n\n // Abort any in-flight exchange when a new one begins\n activeAbortController?.abort()\n const abortController = new AbortController()\n activeAbortController = abortController\n\n try {\n const oneTimeToken = await config.getToken()\n logger.log('[CapitalOS] Received one-time token')\n\n // Check if aborted after getToken\n if (abortController.signal.aborted) {\n return\n }\n\n // Exchange for long-lived token using @capitalos/core\n const result = await exchangeOneTimeToken({\n oneTimeToken,\n enableLogging: config.enableLogging,\n logger: config.logger,\n timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,\n signal: abortController.signal,\n })\n\n // Check if aborted after exchange\n if (abortController.signal.aborted) {\n return\n }\n\n tokenData.value = {\n token: result.longLivedToken,\n tokenType: TokenType.longLived,\n baseUrl: result.baseUrl,\n paramKey: TokenParamKey.accessToken,\n paramLocation: TokenParamLocation.hash,\n }\n logger.log('[CapitalOS] Token exchange complete')\n } catch (e) {\n // Ignore abort errors\n if (e instanceof Error && e.name === 'AbortError') {\n return\n }\n\n const err = toError(e)\n logger.error(`[CapitalOS] Token refresh failed: ${err.message}`)\n error.value = err\n } finally {\n if (activeAbortController === abortController) {\n activeAbortController = null\n }\n isLoading.value = false\n }\n }\n\n // Invalidates the current token, triggering a refresh.\n // Called when the iframe signals that the token has expired.\n function invalidateToken(): void {\n logger.log('[CapitalOS] Invalidating token')\n tokenData.value = undefined\n error.value = undefined\n refreshToken()\n }\n\n // Create state object to provide\n const state: CapitalOsState = {\n tokenData: readonly(tokenData),\n isLoading: readonly(isLoading),\n error: readonly(error),\n invalidateToken,\n config,\n }\n\n // Store globally as fallback for Vue 2.7 edge cases where inject() fails\n globalState = state\n\n // Provide state using the appropriate API based on Vue version\n if (isVue3App(appOrVue)) {\n // Vue 3: use app.provide() - this makes state available to all components\n // via inject(CAPITALOS_INJECTION_KEY)\n appOrVue.provide(CAPITALOS_INJECTION_KEY, state)\n } else {\n // Vue 2.7: use Vue.mixin() to add a provide function to all components.\n // This is the Vue 2 equivalent of app.provide() - it injects the provide\n // option into every component so inject() can find our state.\n appOrVue.mixin({\n provide() {\n return {\n [CAPITALOS_INJECTION_KEY]: state,\n }\n },\n })\n }\n\n // Start token exchange eagerly (matches Angular behavior)\n refreshToken()\n },\n }\n}\n","import type { PropType } from 'vue'\nimport type { ThemeColorScheme } from '@capitalos/core'\nimport { CapitalOSError } from '@capitalos/core'\n\n/**\n * Common props shared by all CapitalOS Vue components.\n */\nexport const commonProps = {\n /**\n * Theme color scheme for the interface.\n * Overrides the theme set in plugin configuration.\n */\n theme: String as PropType<ThemeColorScheme>,\n /**\n * Offset in pixels to add to the iframe height calculation.\n * Useful for avoiding scrollbars when the content height changes dynamically.\n */\n heightOffsetPx: {\n type: Number,\n default: 12,\n },\n /**\n * Whether to enable logging for debugging purposes.\n * Overrides the enableLogging set in plugin configuration.\n */\n enableLogging: Boolean,\n}\n\n/**\n * Common emits shared by all CapitalOS Vue components.\n */\nexport const commonEmits = {\n /**\n * Emitted when the interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n}\n","import { inject, computed, type ComputedRef } from 'vue'\nimport { CAPITALOS_INJECTION_KEY, getGlobalState } from '../plugin'\nimport type { CapitalOsState, AuthState } from '../types'\n\n/**\n * Return type for useCapitalOsAuth composable\n */\nexport interface UseCapitalOsAuthReturn extends CapitalOsState {\n /**\n * Computed authentication state\n */\n authState: ComputedRef<AuthState>\n}\n\n/**\n * @internal\n * Internal composable for accessing CapitalOS authentication state.\n * Used by SDK components - not intended for direct consumer use.\n */\nexport function useCapitalOsAuth(): UseCapitalOsAuthReturn {\n // MAINTAINER NOTE: Vue 2.7 vs Vue 3 Compatibility\n //\n // Try inject() first - this works in:\n // - Vue 3 (via app.provide)\n // - Vue 2.7 (via mixin provide, in most components)\n //\n // Fall back to getGlobalState() for Vue 2.7 edge cases where inject() fails,\n // such as the root component or components created before the mixin is applied.\n //\n // Do NOT remove the ?? getGlobalState() fallback - it's required for Vue 2.7.\n const state = inject<CapitalOsState>(CAPITALOS_INJECTION_KEY) ?? getGlobalState()\n\n if (!state) {\n throw new Error(\n 'useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. ' +\n 'Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts'\n )\n }\n\n const authState = computed<AuthState>(() => {\n if (state.isLoading.value) return 'loading'\n if (state.error.value) return 'error'\n if (state.tokenData.value) return 'authenticated'\n return 'idle'\n })\n\n return {\n ...state,\n authState,\n }\n}\n","{\n \"name\": \"@capitalos/vue\",\n \"version\": \"0.2.0\",\n \"description\": \"Vue SDK for CapitalOS\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.mts\",\n \"default\": \"./dist/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n }\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"dev\": \"tsdown --watch\",\n \"lint\": \"eslint src --ext .ts --fix\",\n \"lint-ci\": \"eslint src --ext .ts\",\n \"start\": \"pnpm --filter vue-test-app dev\",\n \"start:all\": \"concurrently \\\"pnpm --filter sdk-test-server start\\\" \\\"pnpm start\\\"\",\n \"test:pack\": \"pnpm build && pnpm pack\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/CapitalOS/theboss\",\n \"directory\": \"sdk/vue\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/CapitalOS/theboss/issues\"\n },\n \"author\": \"CapitalOS\",\n \"license\": \"MIT\",\n \"peerDependencies\": {\n \"vue\": \"^2.7.0 || ^3.0.0\"\n },\n \"dependencies\": {\n \"@capitalos/core\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"tsdown\": \"^0.9.2\",\n \"typescript\": \"^5.6.2\",\n \"vue\": \"^3.5.13\",\n \"concurrently\": \"^9.1.2\"\n }\n}\n","import { ref, watch, onUnmounted, h, type VNode, type Ref } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type TokenData,\n type RawErrorDetails,\n type RenderingContext,\n type IframeManagerConfig,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from './use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\n\n/**\n * Props expected by useIframeComponent - matches commonProps shape\n */\nexport interface IframeComponentProps {\n theme?: 'light' | 'dark' | 'system'\n heightOffsetPx: number\n enableLogging?: boolean\n}\n\n/**\n * Emit function type for common events\n */\nexport interface CommonEmitFn {\n (event: 'loaded'): void\n (event: 'error', error: CapitalOSError): void\n}\n\n/**\n * Component-specific callbacks to pass to IframeManager.\n * Excludes common callbacks (onLoad, onError, onTokenExpired) which the composable handles.\n */\nexport type ComponentCallbacks = Omit<IframeManagerConfig['callbacks'], 'onLoad' | 'onError' | 'onTokenExpired'>\n\n/**\n * Options for configuring the iframe component\n */\nexport interface UseIframeComponentOptions {\n /** Props from the component */\n props: IframeComponentProps\n /** Emit function from the component */\n emit: CommonEmitFn\n /** Function that returns the rendering context for this component */\n getRenderingContext: () => RenderingContext\n /** Component-specific callbacks (onboarding, tokenExchange, etc.) */\n componentCallbacks?: ComponentCallbacks\n}\n\n/**\n * Return type for useIframeComponent\n */\nexport interface UseIframeComponentReturn {\n /** Ref to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Render function that returns the container div */\n render: () => VNode\n}\n\n/**\n * Composable that handles common iframe component setup.\n *\n * Manages:\n * - Container ref and iframe manager lifecycle\n * - Token data subscription and iframe initialization\n * - Common callbacks (onLoad, onError, onTokenExpired)\n * - Cleanup on unmount\n * - Render function for the container div\n *\n * Note: Rendering context props (e.g., entryPoint, exitPoint for Onboarding) are read once\n * at iframe initialization. Changes to these props after the iframe loads will NOT trigger\n * re-initialization. This matches the behavior of the React SDK.\n *\n * @example\n * ```ts\n * setup(props, { emit }) {\n * const { render } = useIframeComponent({\n * props,\n * emit,\n * getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n * })\n * return render\n * }\n * ```\n */\nexport function useIframeComponent(options: UseIframeComponentOptions): UseIframeComponentReturn {\n const { props, emit, getRenderingContext, componentCallbacks } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const { tokenData, config, invalidateToken } = useCapitalOsAuth()\n\n let iframeManager: IframeManager | null = null\n\n function initializeIframe(token: TokenData, container: HTMLElement): void {\n // Destroy previous iframe before creating new one (matches Angular/React behavior)\n // This ensures token refresh works correctly - new token = new iframe\n iframeManager?.destroy()\n\n const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false\n const resolvedTheme = props.theme ?? config.theme\n\n iframeManager = new IframeManager({\n container,\n tokenData: token,\n renderingContext: getRenderingContext(),\n theme: resolvedTheme,\n enableLogging: resolvedEnableLogging,\n logger: config.logger,\n sdkVersion: SDK_VERSION,\n heightOffsetPx: props.heightOffsetPx,\n callbacks: {\n onLoad: () => {\n emit('loaded')\n },\n onError: (rawError: RawErrorDetails) => {\n emit('error', new CapitalOSError(rawError))\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n ...componentCallbacks,\n },\n })\n }\n\n // Watch for tokenData and container changes to initialize the iframe.\n //\n // Why watch both tokenData AND containerRef?\n // - tokenData: comes from async token exchange, may not be ready on mount\n // - containerRef: DOM element, available after first render\n //\n // Why { immediate: true }?\n // - If tokenData is already available when component mounts, we want to\n // initialize immediately without waiting for a change\n // - Without this, the iframe wouldn't load until tokenData changes again\n watch(\n [tokenData, containerRef],\n ([newTokenData, container]) => {\n if (newTokenData && container) {\n initializeIframe(newTokenData, container)\n }\n },\n { immediate: true }\n )\n\n onUnmounted(() => {\n iframeManager?.destroy()\n iframeManager = null\n })\n\n // Render function - creates the container div for the iframe.\n //\n // Why use a function ref `ref: (el) => { ... }` instead of `ref: containerRef`?\n //\n // Vue 2.7 and Vue 3 handle template refs differently in render functions:\n // - Vue 3: can use `ref: containerRef` directly\n // - Vue 2.7: requires function ref pattern for reliable element access\n //\n // The function ref pattern works in both versions, so we use it for compatibility.\n // Do NOT change this to `ref: containerRef` - it will break Vue 2.7 support.\n //\n // The `as unknown as string` type assertion is required because:\n // - Vue 3's TypeScript types don't properly express that function refs are valid\n // - The function ref pattern IS valid at runtime in both Vue 2.7 and Vue 3\n // - This is a known limitation of Vue 3's type definitions\n function render(): VNode {\n return h('div', {\n // Type assertion required: Vue 3's types don't support function refs in h(),\n // but function refs ARE valid at runtime in both Vue 2.7 and Vue 3\n ref: ((el: Element | null) => {\n containerRef.value = el as HTMLElement | null\n }) as unknown as string,\n class: 'capitalos-iframe-container',\n style: { width: '100%' },\n })\n }\n\n return {\n containerRef,\n render,\n }\n}\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * CardsApp component that renders the CapitalOS cards interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!cardsLoaded\">Loading...</div>\n * <CardsApp\n * @loaded=\"cardsLoaded = true\"\n * @error=\"onCardsError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { CardsApp } from '@capitalos/vue'\n *\n * const cardsLoaded = ref(false)\n * const onCardsError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'cardsApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent } from 'vue'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * BillPayApp component that renders the CapitalOS bill payment interface in an iframe.\n *\n * The component emits events for loading state - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!billPayLoaded\">Loading...</div>\n * <BillPayApp\n * @loaded=\"billPayLoaded = true\"\n * @error=\"onBillPayError\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { BillPayApp } from '@capitalos/vue'\n *\n * const billPayLoaded = ref(false)\n * const onBillPayError = (error) => console.error(error)\n * </script>\n * ```\n */\nexport const BillPayApp = defineComponent({\n name: 'BillPayApp',\n props: commonProps,\n emits: commonEmits,\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({ entryPoint: 'billPayApp' }),\n })\n\n return render\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport type { Account, CopyOverrides, OnboardingEntryPoint, OnboardingExitPoint } from '@capitalos/core'\nimport { commonProps, commonEmits } from './common-props'\nimport { useIframeComponent } from '../composables/use-iframe-component'\n\n/**\n * Onboarding component that renders the CapitalOS onboarding flow in an iframe.\n *\n * The component emits events for loading state and completion - consumers should handle their own loading UI:\n *\n * @example\n * ```vue\n * <template>\n * <div v-if=\"!onboardingLoaded\">Loading...</div>\n * <Onboarding\n * entry-point=\"welcome\"\n * exit-point=\"activation\"\n * @loaded=\"onboardingLoaded = true\"\n * @error=\"onOnboardingError\"\n * @done=\"onOnboardingComplete\"\n * />\n * </template>\n *\n * <script setup>\n * import { ref } from 'vue'\n * import { Onboarding } from '@capitalos/vue'\n *\n * const onboardingLoaded = ref(false)\n * const onOnboardingError = (error) => console.error(error)\n * const onOnboardingComplete = (account) => {\n * console.log('Onboarding complete:', account.status)\n * }\n * </script>\n * ```\n */\nexport const Onboarding = defineComponent({\n name: 'Onboarding',\n props: {\n ...commonProps,\n /**\n * The starting point of the onboarding flow.\n * @default 'welcome'\n */\n entryPoint: String as PropType<OnboardingEntryPoint>,\n /**\n * The ending point of the onboarding flow.\n * @default 'activation'\n */\n exitPoint: String as PropType<OnboardingExitPoint>,\n /**\n * Whether to allow the user to exit the onboarding flow by clicking the done button\n * when they finished the onboarding form but are still not approved (e.g. waiting or pending).\n * This should only be false if you want the user to stay on the onboarding flow\n * (e.g. to add bank account) and not exit the flow.\n * @default true\n */\n allowExitOnNonApproved: {\n type: Boolean,\n default: undefined,\n },\n /**\n * Custom text for the final \"done\" button shown at the end of the onboarding flow.\n * If not provided, the default text for each exit screen is used.\n */\n doneButtonText: String as PropType<string>,\n },\n emits: {\n ...commonEmits,\n /**\n * Emitted when the onboarding flow completes successfully.\n */\n done: (_account: Account) => true,\n },\n setup(props, { emit }) {\n const { render } = useIframeComponent({\n props,\n emit,\n getRenderingContext: () => ({\n entryPoint: 'onboarding',\n onboardingEntryPoint: props.entryPoint,\n onboardingExitPoint: props.exitPoint,\n allowExitOnNonApproved: props.allowExitOnNonApproved,\n copy: props.doneButtonText\n ? ({\n 'onboarding.doneButton': props.doneButtonText,\n 'onboarding.continueButton': props.doneButtonText,\n 'activation.successButton': props.doneButtonText,\n } satisfies CopyOverrides)\n : undefined,\n }),\n componentCallbacks: {\n onboarding: {\n onDone: (account: Account) => {\n emit('done', account)\n },\n },\n },\n })\n\n return render\n },\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capitalos/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Vue SDK for CapitalOS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -20,16 +20,6 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "tsdown",
|
|
25
|
-
"dev": "tsdown --watch",
|
|
26
|
-
"lint": "eslint src --ext .ts --fix",
|
|
27
|
-
"lint-ci": "eslint src --ext .ts",
|
|
28
|
-
"start": "pnpm --filter vue-test-app dev",
|
|
29
|
-
"start:all": "concurrently \"pnpm --filter sdk-test-server start\" \"pnpm start\"",
|
|
30
|
-
"test:pack": "pnpm build && pnpm pack",
|
|
31
|
-
"prepublishOnly": "pnpm build"
|
|
32
|
-
},
|
|
33
23
|
"keywords": [
|
|
34
24
|
"capitalos",
|
|
35
25
|
"vue",
|
|
@@ -50,12 +40,21 @@
|
|
|
50
40
|
"vue": "^2.7.0 || ^3.0.0"
|
|
51
41
|
},
|
|
52
42
|
"dependencies": {
|
|
53
|
-
"@capitalos/core": "
|
|
43
|
+
"@capitalos/core": "0.2.0"
|
|
54
44
|
},
|
|
55
45
|
"devDependencies": {
|
|
56
46
|
"tsdown": "^0.9.2",
|
|
57
47
|
"typescript": "^5.6.2",
|
|
58
48
|
"vue": "^3.5.13",
|
|
59
49
|
"concurrently": "^9.1.2"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsdown",
|
|
53
|
+
"dev": "tsdown --watch",
|
|
54
|
+
"lint": "eslint src --ext .ts --fix",
|
|
55
|
+
"lint-ci": "eslint src --ext .ts",
|
|
56
|
+
"start": "pnpm --filter vue-test-app dev",
|
|
57
|
+
"start:all": "concurrently \"pnpm --filter sdk-test-server start\" \"pnpm start\"",
|
|
58
|
+
"test:pack": "pnpm build && pnpm pack"
|
|
60
59
|
}
|
|
61
|
-
}
|
|
60
|
+
}
|