@capitalos/vue 0.1.0-rc.1
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/README.md +75 -0
- package/dist/index.d.mts +52 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +241 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# CapitalOS for Vue
|
|
2
|
+
|
|
3
|
+
Vue components for integrating with CapitalOS
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
Vue 2.7.0 or later
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### npm
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @capitalos/vue
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### yarn
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @capitalos/vue
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### pnpm
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @capitalos/vue
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
Please refer to the [official docs](https://docs.capitalos.com/docs/using-vue-client-library) for more details.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
In order to use the components you will need to obtain a client authentication token. Refer to the [CapitalOS documentation](https://docs.capitalos.com/docs/using-vue-client-library) for more information.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// main.ts
|
|
39
|
+
import { createApp } from 'vue'
|
|
40
|
+
import { createCapitalOs } from '@capitalos/vue'
|
|
41
|
+
import App from './App.vue'
|
|
42
|
+
|
|
43
|
+
const app = createApp(App)
|
|
44
|
+
|
|
45
|
+
app.use(
|
|
46
|
+
createCapitalOs({
|
|
47
|
+
getToken: async () => {
|
|
48
|
+
// Call your backend which initiates login and returns a one-time token
|
|
49
|
+
const res = await fetch('/api/capitalos/token', { method: 'POST' })
|
|
50
|
+
const json = await res.json()
|
|
51
|
+
return json.token
|
|
52
|
+
},
|
|
53
|
+
enableLogging: true,
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
app.mount('#app')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<!-- MyComponent.vue -->
|
|
62
|
+
<template>
|
|
63
|
+
<CardsApp @loaded="onLoaded" @error="onError" />
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<script setup>
|
|
67
|
+
import { CardsApp } from '@capitalos/vue'
|
|
68
|
+
const onLoaded = () => console.log('CardsApp loaded!')
|
|
69
|
+
const onError = (error) => console.error('CardsApp error:', error)
|
|
70
|
+
</script>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## TypeScript support
|
|
74
|
+
|
|
75
|
+
TypeScript definitions for `@capitalos/vue` are built into the npm package and should be automatically picked up by your editor.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ComputedRef, DeepReadonly, Ref } from "vue";
|
|
2
|
+
import { CapitalOSError, ErrorCode, Logger, Logger as Logger$1, RawErrorDetails, ThemeColorScheme, ThemeColorScheme as ThemeColorScheme$1, TokenData, TokenData as TokenData$1 } from "@capitalos/core";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
interface CapitalOsConfig {
|
|
6
|
+
getToken: () => Promise<string>;
|
|
7
|
+
enableLogging?: boolean;
|
|
8
|
+
logger?: Logger$1;
|
|
9
|
+
theme?: ThemeColorScheme$1;
|
|
10
|
+
}
|
|
11
|
+
interface CapitalOsState {
|
|
12
|
+
tokenData: DeepReadonly<Ref<TokenData$1 | undefined>>;
|
|
13
|
+
isLoading: DeepReadonly<Ref<boolean>>;
|
|
14
|
+
error: DeepReadonly<Ref<Error | undefined>>;
|
|
15
|
+
invalidateToken: () => void;
|
|
16
|
+
config: CapitalOsConfig;
|
|
17
|
+
}
|
|
18
|
+
type AuthState = 'idle' | 'loading' | 'authenticated' | 'error';
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/plugin.d.ts
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
23
|
+
declare const CAPITALOS_INJECTION_KEY: unique symbol;
|
|
24
|
+
interface Vue2Constructor {
|
|
25
|
+
mixin: (mixin: Record<string, unknown>) => void;
|
|
26
|
+
version: string;
|
|
27
|
+
}
|
|
28
|
+
interface Vue3App {
|
|
29
|
+
provide: (key: symbol, value: unknown) => void;
|
|
30
|
+
version: string;
|
|
31
|
+
}
|
|
32
|
+
declare function createCapitalOs(config: CapitalOsConfig): {
|
|
33
|
+
install(appOrVue: Vue2Constructor | Vue3App): void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/components/CardsApp.d.ts
|
|
38
|
+
declare const CardsApp: any;
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/composables/use-capitalos-auth.d.ts
|
|
42
|
+
//# sourceMappingURL=CardsApp.d.ts.map
|
|
43
|
+
interface UseCapitalOsAuthReturn extends CapitalOsState {
|
|
44
|
+
authState: ComputedRef<AuthState>;
|
|
45
|
+
}
|
|
46
|
+
declare function useCapitalOsAuth(): UseCapitalOsAuthReturn;
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//# sourceMappingURL=use-capitalos-auth.d.ts.map
|
|
50
|
+
|
|
51
|
+
export { AuthState, CAPITALOS_INJECTION_KEY, CapitalOSError, CapitalOsConfig, CardsApp, ErrorCode, Logger, RawErrorDetails, ThemeColorScheme, TokenData, UseCapitalOsAuthReturn, createCapitalOs, useCapitalOsAuth };
|
|
52
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/plugin.ts","../src/components/CardsApp.ts","../src/composables/use-capitalos-auth.ts"],"sourcesContent":null,"mappings":";;;;UAMiB,eAAA;kBAKC;EALD,aAAA,CAAA,EAAA,OAAe;EAAA,MAAA,CAAA,EAerB,QAfqB;EAAA,KAKd,CAAA,EAeR,kBAfQ;;AAeR,UAMO,cAAA,CANP;EAAgB,SAAA,EAOb,YAPa,CAOA,GAPA,CAOI,WAPJ,GAAA,SAAA,CAAA,CAAA;EAMT,SAAA,EAEJ,YAFkB,CAEL,GAFK,CAAA,OAAA,CAAA,CAAA;EAAA,KAAA,EAGtB,YAHsB,CAGT,GAHS,CAGL,KAHK,GAAA,SAAA,CAAA,CAAA;EAAA,eACD,EAAA,GAAA,GAAA,IAAA;EAAS,MAAb,EAIhB,eAJgB;;AACA,KASd,SAAA,GATc,MAAA,GAAA,SAAA,GAAA,eAAA,GAAA,OAAA;;;;;cCSb;UAsCH,eAAA;ED3EO,KAAA,EAAA,CAAA,KAAA,EC4EA,MD5Ee,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,IAAA;EAAA,OAAA,EAAA,MAAA;;UCkFtB,OAAA,CDnEC;EAAM,OAKP,EAAA,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAAgB,OAAA,EAAA,MAAA;AAM1B;AAA+B,iBCyFf,eAAA,CDzFe,MAAA,ECyFS,eDzFT,CAAA,EAAA;EAAA,OACD,CAAA,QAAA,EC0FR,eD1FQ,GC0FU,OD1FV,CAAA,EAAA,IAAA;CAAS;;;;cEqB1B;;;;;UC/CI,sBAAA,SAA+B;aAInC,YAAY;AHLzB;AAAgC,iBGahB,gBAAA,CAAA,CHbgB,EGaI,sBHbJ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ComputedRef, DeepReadonly, Ref } from "vue";
|
|
2
|
+
import { CapitalOSError, ErrorCode, Logger, Logger as Logger$1, RawErrorDetails, ThemeColorScheme, ThemeColorScheme as ThemeColorScheme$1, TokenData, TokenData as TokenData$1 } from "@capitalos/core";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
interface CapitalOsConfig {
|
|
6
|
+
getToken: () => Promise<string>;
|
|
7
|
+
enableLogging?: boolean;
|
|
8
|
+
logger?: Logger$1;
|
|
9
|
+
theme?: ThemeColorScheme$1;
|
|
10
|
+
}
|
|
11
|
+
interface CapitalOsState {
|
|
12
|
+
tokenData: DeepReadonly<Ref<TokenData$1 | undefined>>;
|
|
13
|
+
isLoading: DeepReadonly<Ref<boolean>>;
|
|
14
|
+
error: DeepReadonly<Ref<Error | undefined>>;
|
|
15
|
+
invalidateToken: () => void;
|
|
16
|
+
config: CapitalOsConfig;
|
|
17
|
+
}
|
|
18
|
+
type AuthState = 'idle' | 'loading' | 'authenticated' | 'error';
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/plugin.d.ts
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
23
|
+
declare const CAPITALOS_INJECTION_KEY: unique symbol;
|
|
24
|
+
interface Vue2Constructor {
|
|
25
|
+
mixin: (mixin: Record<string, unknown>) => void;
|
|
26
|
+
version: string;
|
|
27
|
+
}
|
|
28
|
+
interface Vue3App {
|
|
29
|
+
provide: (key: symbol, value: unknown) => void;
|
|
30
|
+
version: string;
|
|
31
|
+
}
|
|
32
|
+
declare function createCapitalOs(config: CapitalOsConfig): {
|
|
33
|
+
install(appOrVue: Vue2Constructor | Vue3App): void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/components/CardsApp.d.ts
|
|
38
|
+
declare const CardsApp: any;
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/composables/use-capitalos-auth.d.ts
|
|
42
|
+
//# sourceMappingURL=CardsApp.d.ts.map
|
|
43
|
+
interface UseCapitalOsAuthReturn extends CapitalOsState {
|
|
44
|
+
authState: ComputedRef<AuthState>;
|
|
45
|
+
}
|
|
46
|
+
declare function useCapitalOsAuth(): UseCapitalOsAuthReturn;
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//# sourceMappingURL=use-capitalos-auth.d.ts.map
|
|
50
|
+
|
|
51
|
+
export { AuthState, CAPITALOS_INJECTION_KEY, CapitalOSError, CapitalOsConfig, CardsApp, ErrorCode, Logger, RawErrorDetails, ThemeColorScheme, TokenData, UseCapitalOsAuthReturn, createCapitalOs, useCapitalOsAuth };
|
|
52
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/plugin.ts","../src/components/CardsApp.ts","../src/composables/use-capitalos-auth.ts"],"sourcesContent":null,"mappings":";;;;UAMiB,eAAA;kBAKC;EALD,aAAA,CAAA,EAAA,OAAe;EAAA,MAAA,CAAA,EAerB,QAfqB;EAAA,KAKd,CAAA,EAeR,kBAfQ;;AAeR,UAMO,cAAA,CANP;EAAgB,SAAA,EAOb,YAPa,CAOA,GAPA,CAOI,WAPJ,GAAA,SAAA,CAAA,CAAA;EAMT,SAAA,EAEJ,YAFkB,CAEL,GAFK,CAAA,OAAA,CAAA,CAAA;EAAA,KAAA,EAGtB,YAHsB,CAGT,GAHS,CAGL,KAHK,GAAA,SAAA,CAAA,CAAA;EAAA,eACD,EAAA,GAAA,GAAA,IAAA;EAAS,MAAb,EAIhB,eAJgB;;AACA,KASd,SAAA,GATc,MAAA,GAAA,SAAA,GAAA,eAAA,GAAA,OAAA;;;;;cCSb;UAsCH,eAAA;ED3EO,KAAA,EAAA,CAAA,KAAA,EC4EA,MD5Ee,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,IAAA;EAAA,OAAA,EAAA,MAAA;;UCkFtB,OAAA,CDnEC;EAAM,OAKP,EAAA,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAAgB,OAAA,EAAA,MAAA;AAM1B;AAA+B,iBCyFf,eAAA,CDzFe,MAAA,ECyFS,eDzFT,CAAA,EAAA;EAAA,OACD,CAAA,QAAA,EC0FR,eD1FQ,GC0FU,OD1FV,CAAA,EAAA,IAAA;CAAS;;;;cEqB1B;;;;;UC/CI,sBAAA,SAA+B;aAInC,YAAY;AHLzB;AAAgC,iBGahB,gBAAA,CAAA,CHbgB,EGaI,sBHbJ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
const vue = __toESM(require("vue"));
|
|
26
|
+
const __capitalos_core = __toESM(require("@capitalos/core"));
|
|
27
|
+
|
|
28
|
+
//#region src/plugin.ts
|
|
29
|
+
/**
|
|
30
|
+
* Injection key for CapitalOS state.
|
|
31
|
+
* Used with Vue's provide/inject system.
|
|
32
|
+
*/
|
|
33
|
+
const CAPITALOS_INJECTION_KEY = Symbol("capitalos");
|
|
34
|
+
let globalState = null;
|
|
35
|
+
function getGlobalState() {
|
|
36
|
+
return globalState;
|
|
37
|
+
}
|
|
38
|
+
const TOKEN_EXCHANGE_TIMEOUT_MS = 1e4;
|
|
39
|
+
function toError(error) {
|
|
40
|
+
if (error instanceof Error) return error;
|
|
41
|
+
return new Error(String(error));
|
|
42
|
+
}
|
|
43
|
+
let installed = false;
|
|
44
|
+
function isVue3App(appOrVue) {
|
|
45
|
+
return typeof appOrVue.provide === "function";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates the CapitalOS Vue plugin.
|
|
49
|
+
*
|
|
50
|
+
* Usage (Vue 3):
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { createApp } from 'vue'
|
|
53
|
+
* import { createCapitalOs } from '@capitalos/vue'
|
|
54
|
+
*
|
|
55
|
+
* const app = createApp(App)
|
|
56
|
+
* app.use(createCapitalOs({ getToken: async () => { ... } }))
|
|
57
|
+
* app.mount('#app')
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Usage (Vue 2.7):
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import Vue from 'vue'
|
|
63
|
+
* import { createCapitalOs } from '@capitalos/vue'
|
|
64
|
+
*
|
|
65
|
+
* Vue.use(createCapitalOs({ getToken: async () => { ... } }))
|
|
66
|
+
* new Vue({ render: h => h(App) }).$mount('#app')
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
function createCapitalOs(config) {
|
|
70
|
+
return { install(appOrVue) {
|
|
71
|
+
if (installed) {
|
|
72
|
+
const logger$1 = (0, __capitalos_core.createLogger)(config.enableLogging, config.logger);
|
|
73
|
+
logger$1.warn("[CapitalOS] Plugin already installed, skipping duplicate installation");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
installed = true;
|
|
77
|
+
const logger = (0, __capitalos_core.createLogger)(config.enableLogging, config.logger);
|
|
78
|
+
const tokenData = (0, vue.ref)(void 0);
|
|
79
|
+
const isLoading = (0, vue.ref)(false);
|
|
80
|
+
const error = (0, vue.ref)(void 0);
|
|
81
|
+
let activeAbortController = null;
|
|
82
|
+
async function refreshToken() {
|
|
83
|
+
if (isLoading.value) {
|
|
84
|
+
logger.log("[CapitalOS] Token refresh already in progress, skipping");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
logger.log("[CapitalOS] Starting token refresh");
|
|
88
|
+
isLoading.value = true;
|
|
89
|
+
error.value = void 0;
|
|
90
|
+
activeAbortController?.abort();
|
|
91
|
+
const abortController = new AbortController();
|
|
92
|
+
activeAbortController = abortController;
|
|
93
|
+
try {
|
|
94
|
+
const oneTimeToken = await config.getToken();
|
|
95
|
+
logger.log("[CapitalOS] Received one-time token");
|
|
96
|
+
if (abortController.signal.aborted) return;
|
|
97
|
+
const result = await (0, __capitalos_core.exchangeOneTimeToken)({
|
|
98
|
+
oneTimeToken,
|
|
99
|
+
enableLogging: config.enableLogging,
|
|
100
|
+
logger: config.logger,
|
|
101
|
+
timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,
|
|
102
|
+
signal: abortController.signal
|
|
103
|
+
});
|
|
104
|
+
if (abortController.signal.aborted) return;
|
|
105
|
+
tokenData.value = {
|
|
106
|
+
token: result.longLivedToken,
|
|
107
|
+
tokenType: __capitalos_core.TokenType.longLived,
|
|
108
|
+
baseUrl: result.baseUrl,
|
|
109
|
+
paramKey: __capitalos_core.TokenParamKey.accessToken,
|
|
110
|
+
paramLocation: __capitalos_core.TokenParamLocation.hash
|
|
111
|
+
};
|
|
112
|
+
logger.log("[CapitalOS] Token exchange complete");
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (e instanceof Error && e.name === "AbortError") return;
|
|
115
|
+
const err = toError(e);
|
|
116
|
+
logger.error(`[CapitalOS] Token refresh failed: ${err.message}`);
|
|
117
|
+
error.value = err;
|
|
118
|
+
} finally {
|
|
119
|
+
if (activeAbortController === abortController) activeAbortController = null;
|
|
120
|
+
isLoading.value = false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function invalidateToken() {
|
|
124
|
+
logger.log("[CapitalOS] Invalidating token");
|
|
125
|
+
tokenData.value = void 0;
|
|
126
|
+
error.value = void 0;
|
|
127
|
+
refreshToken();
|
|
128
|
+
}
|
|
129
|
+
const state = {
|
|
130
|
+
tokenData: (0, vue.readonly)(tokenData),
|
|
131
|
+
isLoading: (0, vue.readonly)(isLoading),
|
|
132
|
+
error: (0, vue.readonly)(error),
|
|
133
|
+
invalidateToken,
|
|
134
|
+
config
|
|
135
|
+
};
|
|
136
|
+
globalState = state;
|
|
137
|
+
if (isVue3App(appOrVue)) appOrVue.provide(CAPITALOS_INJECTION_KEY, state);
|
|
138
|
+
else appOrVue.mixin({ provide() {
|
|
139
|
+
return { [CAPITALOS_INJECTION_KEY]: state };
|
|
140
|
+
} });
|
|
141
|
+
refreshToken();
|
|
142
|
+
} };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/composables/use-capitalos-auth.ts
|
|
147
|
+
/**
|
|
148
|
+
* @internal
|
|
149
|
+
* Internal composable for accessing CapitalOS authentication state.
|
|
150
|
+
* Used by SDK components - not intended for direct consumer use.
|
|
151
|
+
*/
|
|
152
|
+
function useCapitalOsAuth() {
|
|
153
|
+
const state = (0, vue.inject)(CAPITALOS_INJECTION_KEY) ?? getGlobalState();
|
|
154
|
+
if (!state) throw new Error("useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts");
|
|
155
|
+
const authState = (0, vue.computed)(() => {
|
|
156
|
+
if (state.isLoading.value) return "loading";
|
|
157
|
+
if (state.error.value) return "error";
|
|
158
|
+
if (state.tokenData.value) return "authenticated";
|
|
159
|
+
return "idle";
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
authState
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region package.json
|
|
169
|
+
var version = "0.1.0-rc.1";
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/components/CardsApp.ts
|
|
173
|
+
/**
|
|
174
|
+
* CardsApp component that renders the CapitalOS cards interface in an iframe.
|
|
175
|
+
*
|
|
176
|
+
* The component emits events for loading state - consumers should handle their own loading UI:
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```vue
|
|
180
|
+
* <template>
|
|
181
|
+
* <div v-if="!cardsLoaded">Loading...</div>
|
|
182
|
+
* <CardsApp
|
|
183
|
+
* @loaded="cardsLoaded = true"
|
|
184
|
+
* @error="onCardsError"
|
|
185
|
+
* />
|
|
186
|
+
* </template>
|
|
187
|
+
*
|
|
188
|
+
* <script setup>
|
|
189
|
+
* import { ref } from 'vue'
|
|
190
|
+
* import { CardsApp } from '@capitalos/vue'
|
|
191
|
+
*
|
|
192
|
+
* const cardsLoaded = ref(false)
|
|
193
|
+
* const onCardsError = (error) => console.error(error)
|
|
194
|
+
* </script>
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* Note: CardsApp is not designed to be conditionally mounted/unmounted frequently.
|
|
198
|
+
* For show/hide behavior, consumers should toggle visibility (e.g., v-show) rather
|
|
199
|
+
* than mount/unmount repeatedly.
|
|
200
|
+
*/
|
|
201
|
+
const CardsApp = (0, vue.defineComponent)({
|
|
202
|
+
name: "CardsApp",
|
|
203
|
+
props: {
|
|
204
|
+
theme: String,
|
|
205
|
+
heightOffsetPx: {
|
|
206
|
+
type: Number,
|
|
207
|
+
default: 12
|
|
208
|
+
},
|
|
209
|
+
enableLogging: Boolean
|
|
210
|
+
},
|
|
211
|
+
emits: {
|
|
212
|
+
loaded: () => true,
|
|
213
|
+
error: (error) => error instanceof __capitalos_core.CapitalOSError
|
|
214
|
+
},
|
|
215
|
+
setup(props, { emit }) {
|
|
216
|
+
const containerRef = (0, vue.ref)(null);
|
|
217
|
+
const { tokenData, config, invalidateToken } = useCapitalOsAuth();
|
|
218
|
+
let iframeManager = null;
|
|
219
|
+
function initializeIframe(token, container) {
|
|
220
|
+
iframeManager?.destroy();
|
|
221
|
+
const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false;
|
|
222
|
+
const resolvedTheme = props.theme ?? config.theme;
|
|
223
|
+
iframeManager = new __capitalos_core.IframeManager({
|
|
224
|
+
container,
|
|
225
|
+
tokenData: token,
|
|
226
|
+
renderingContext: { entryPoint: "cardsApp" },
|
|
227
|
+
theme: resolvedTheme,
|
|
228
|
+
enableLogging: resolvedEnableLogging,
|
|
229
|
+
logger: config.logger,
|
|
230
|
+
sdkVersion: version,
|
|
231
|
+
heightOffsetPx: props.heightOffsetPx,
|
|
232
|
+
callbacks: {
|
|
233
|
+
onLoad: () => {
|
|
234
|
+
emit("loaded");
|
|
235
|
+
},
|
|
236
|
+
onError: (rawError) => {
|
|
237
|
+
const capitalOsError = new __capitalos_core.CapitalOSError(rawError);
|
|
238
|
+
emit("error", capitalOsError);
|
|
239
|
+
},
|
|
240
|
+
onTokenExpired: () => {
|
|
241
|
+
invalidateToken();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
(0, vue.watch)([tokenData, containerRef], ([newTokenData, container]) => {
|
|
247
|
+
if (newTokenData && container) initializeIframe(newTokenData, container);
|
|
248
|
+
}, { immediate: true });
|
|
249
|
+
(0, vue.onUnmounted)(() => {
|
|
250
|
+
iframeManager?.destroy();
|
|
251
|
+
iframeManager = null;
|
|
252
|
+
});
|
|
253
|
+
return () => (0, vue.h)("div", {
|
|
254
|
+
ref: (el) => {
|
|
255
|
+
containerRef.value = el;
|
|
256
|
+
},
|
|
257
|
+
class: "capitalos-iframe-container",
|
|
258
|
+
style: { width: "100%" }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
264
|
+
exports.CAPITALOS_INJECTION_KEY = CAPITALOS_INJECTION_KEY
|
|
265
|
+
Object.defineProperty(exports, 'CapitalOSError', {
|
|
266
|
+
enumerable: true,
|
|
267
|
+
get: function () {
|
|
268
|
+
return __capitalos_core.CapitalOSError;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
exports.CardsApp = CardsApp
|
|
272
|
+
Object.defineProperty(exports, 'ErrorCode', {
|
|
273
|
+
enumerable: true,
|
|
274
|
+
get: function () {
|
|
275
|
+
return __capitalos_core.ErrorCode;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
exports.createCapitalOs = createCapitalOs
|
|
279
|
+
exports.useCapitalOsAuth = useCapitalOsAuth
|
|
280
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","IframeManager","SDK_VERSION","rawError: RawErrorDetails","el: Element | null"],"sources":["../src/plugin.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/components/CardsApp.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 { 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-rc.1\",\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 },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\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","// =============================================================================\n// MAINTAINER NOTE: Why defineComponent + render function instead of SFC?\n// =============================================================================\n//\n// This component uses `defineComponent()` with a render function (`h()`) instead\n// of a Single File Component (.vue file) for Vue 2.7/3 compatibility:\n//\n// - SFCs require different compilers for Vue 2 vs Vue 3\n// - Render functions work identically in both versions\n// - Simpler build - no vue-compiler needed, just TypeScript\n//\n// This is a deliberate architectural choice for cross-version compatibility.\n// Do NOT convert this to an SFC without understanding the implications.\n// =============================================================================\n\nimport { defineComponent, h, ref, watch, onUnmounted, type PropType } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type ThemeColorScheme,\n type TokenData,\n type RawErrorDetails,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from '../composables/use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\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 *\n * Note: CardsApp is not designed to be conditionally mounted/unmounted frequently.\n * For show/hide behavior, consumers should toggle visibility (e.g., v-show) rather\n * than mount/unmount repeatedly.\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: {\n /**\n * Theme color scheme for the cards 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 emits: {\n /**\n * Emitted when the cards interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the cards interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n },\n setup(props, { emit }) {\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: { entryPoint: 'cardsApp' },\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 const capitalOsError = new CapitalOSError(rawError)\n emit('error', capitalOsError)\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n },\n })\n }\n\n // MAINTAINER NOTE: 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 // MAINTAINER NOTE: 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 return () =>\n 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"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { computed, defineComponent, h, inject, onUnmounted, readonly, ref, watch } from "vue";
|
|
2
|
+
import { CapitalOSError, CapitalOSError as CapitalOSError$1, ErrorCode, IframeManager, TokenParamKey, TokenParamLocation, TokenType, createLogger, exchangeOneTimeToken } from "@capitalos/core";
|
|
3
|
+
|
|
4
|
+
//#region src/plugin.ts
|
|
5
|
+
/**
|
|
6
|
+
* Injection key for CapitalOS state.
|
|
7
|
+
* Used with Vue's provide/inject system.
|
|
8
|
+
*/
|
|
9
|
+
const CAPITALOS_INJECTION_KEY = Symbol("capitalos");
|
|
10
|
+
let globalState = null;
|
|
11
|
+
function getGlobalState() {
|
|
12
|
+
return globalState;
|
|
13
|
+
}
|
|
14
|
+
const TOKEN_EXCHANGE_TIMEOUT_MS = 1e4;
|
|
15
|
+
function toError(error) {
|
|
16
|
+
if (error instanceof Error) return error;
|
|
17
|
+
return new Error(String(error));
|
|
18
|
+
}
|
|
19
|
+
let installed = false;
|
|
20
|
+
function isVue3App(appOrVue) {
|
|
21
|
+
return typeof appOrVue.provide === "function";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates the CapitalOS Vue plugin.
|
|
25
|
+
*
|
|
26
|
+
* Usage (Vue 3):
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { createApp } from 'vue'
|
|
29
|
+
* import { createCapitalOs } from '@capitalos/vue'
|
|
30
|
+
*
|
|
31
|
+
* const app = createApp(App)
|
|
32
|
+
* app.use(createCapitalOs({ getToken: async () => { ... } }))
|
|
33
|
+
* app.mount('#app')
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Usage (Vue 2.7):
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import Vue from 'vue'
|
|
39
|
+
* import { createCapitalOs } from '@capitalos/vue'
|
|
40
|
+
*
|
|
41
|
+
* Vue.use(createCapitalOs({ getToken: async () => { ... } }))
|
|
42
|
+
* new Vue({ render: h => h(App) }).$mount('#app')
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function createCapitalOs(config) {
|
|
46
|
+
return { install(appOrVue) {
|
|
47
|
+
if (installed) {
|
|
48
|
+
const logger$1 = createLogger(config.enableLogging, config.logger);
|
|
49
|
+
logger$1.warn("[CapitalOS] Plugin already installed, skipping duplicate installation");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
installed = true;
|
|
53
|
+
const logger = createLogger(config.enableLogging, config.logger);
|
|
54
|
+
const tokenData = ref(void 0);
|
|
55
|
+
const isLoading = ref(false);
|
|
56
|
+
const error = ref(void 0);
|
|
57
|
+
let activeAbortController = null;
|
|
58
|
+
async function refreshToken() {
|
|
59
|
+
if (isLoading.value) {
|
|
60
|
+
logger.log("[CapitalOS] Token refresh already in progress, skipping");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
logger.log("[CapitalOS] Starting token refresh");
|
|
64
|
+
isLoading.value = true;
|
|
65
|
+
error.value = void 0;
|
|
66
|
+
activeAbortController?.abort();
|
|
67
|
+
const abortController = new AbortController();
|
|
68
|
+
activeAbortController = abortController;
|
|
69
|
+
try {
|
|
70
|
+
const oneTimeToken = await config.getToken();
|
|
71
|
+
logger.log("[CapitalOS] Received one-time token");
|
|
72
|
+
if (abortController.signal.aborted) return;
|
|
73
|
+
const result = await exchangeOneTimeToken({
|
|
74
|
+
oneTimeToken,
|
|
75
|
+
enableLogging: config.enableLogging,
|
|
76
|
+
logger: config.logger,
|
|
77
|
+
timeoutMs: TOKEN_EXCHANGE_TIMEOUT_MS,
|
|
78
|
+
signal: abortController.signal
|
|
79
|
+
});
|
|
80
|
+
if (abortController.signal.aborted) return;
|
|
81
|
+
tokenData.value = {
|
|
82
|
+
token: result.longLivedToken,
|
|
83
|
+
tokenType: TokenType.longLived,
|
|
84
|
+
baseUrl: result.baseUrl,
|
|
85
|
+
paramKey: TokenParamKey.accessToken,
|
|
86
|
+
paramLocation: TokenParamLocation.hash
|
|
87
|
+
};
|
|
88
|
+
logger.log("[CapitalOS] Token exchange complete");
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (e instanceof Error && e.name === "AbortError") return;
|
|
91
|
+
const err = toError(e);
|
|
92
|
+
logger.error(`[CapitalOS] Token refresh failed: ${err.message}`);
|
|
93
|
+
error.value = err;
|
|
94
|
+
} finally {
|
|
95
|
+
if (activeAbortController === abortController) activeAbortController = null;
|
|
96
|
+
isLoading.value = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function invalidateToken() {
|
|
100
|
+
logger.log("[CapitalOS] Invalidating token");
|
|
101
|
+
tokenData.value = void 0;
|
|
102
|
+
error.value = void 0;
|
|
103
|
+
refreshToken();
|
|
104
|
+
}
|
|
105
|
+
const state = {
|
|
106
|
+
tokenData: readonly(tokenData),
|
|
107
|
+
isLoading: readonly(isLoading),
|
|
108
|
+
error: readonly(error),
|
|
109
|
+
invalidateToken,
|
|
110
|
+
config
|
|
111
|
+
};
|
|
112
|
+
globalState = state;
|
|
113
|
+
if (isVue3App(appOrVue)) appOrVue.provide(CAPITALOS_INJECTION_KEY, state);
|
|
114
|
+
else appOrVue.mixin({ provide() {
|
|
115
|
+
return { [CAPITALOS_INJECTION_KEY]: state };
|
|
116
|
+
} });
|
|
117
|
+
refreshToken();
|
|
118
|
+
} };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/composables/use-capitalos-auth.ts
|
|
123
|
+
/**
|
|
124
|
+
* @internal
|
|
125
|
+
* Internal composable for accessing CapitalOS authentication state.
|
|
126
|
+
* Used by SDK components - not intended for direct consumer use.
|
|
127
|
+
*/
|
|
128
|
+
function useCapitalOsAuth() {
|
|
129
|
+
const state = inject(CAPITALOS_INJECTION_KEY) ?? getGlobalState();
|
|
130
|
+
if (!state) throw new Error("useCapitalOsAuth must be used in a component where createCapitalOs plugin is installed. Make sure to call app.use(createCapitalOs({ ... })) or Vue.use(createCapitalOs({ ... })) in your main.ts");
|
|
131
|
+
const authState = computed(() => {
|
|
132
|
+
if (state.isLoading.value) return "loading";
|
|
133
|
+
if (state.error.value) return "error";
|
|
134
|
+
if (state.tokenData.value) return "authenticated";
|
|
135
|
+
return "idle";
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
...state,
|
|
139
|
+
authState
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region package.json
|
|
145
|
+
var version = "0.1.0-rc.1";
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/components/CardsApp.ts
|
|
149
|
+
/**
|
|
150
|
+
* CardsApp component that renders the CapitalOS cards interface in an iframe.
|
|
151
|
+
*
|
|
152
|
+
* The component emits events for loading state - consumers should handle their own loading UI:
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```vue
|
|
156
|
+
* <template>
|
|
157
|
+
* <div v-if="!cardsLoaded">Loading...</div>
|
|
158
|
+
* <CardsApp
|
|
159
|
+
* @loaded="cardsLoaded = true"
|
|
160
|
+
* @error="onCardsError"
|
|
161
|
+
* />
|
|
162
|
+
* </template>
|
|
163
|
+
*
|
|
164
|
+
* <script setup>
|
|
165
|
+
* import { ref } from 'vue'
|
|
166
|
+
* import { CardsApp } from '@capitalos/vue'
|
|
167
|
+
*
|
|
168
|
+
* const cardsLoaded = ref(false)
|
|
169
|
+
* const onCardsError = (error) => console.error(error)
|
|
170
|
+
* </script>
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* Note: CardsApp is not designed to be conditionally mounted/unmounted frequently.
|
|
174
|
+
* For show/hide behavior, consumers should toggle visibility (e.g., v-show) rather
|
|
175
|
+
* than mount/unmount repeatedly.
|
|
176
|
+
*/
|
|
177
|
+
const CardsApp = defineComponent({
|
|
178
|
+
name: "CardsApp",
|
|
179
|
+
props: {
|
|
180
|
+
theme: String,
|
|
181
|
+
heightOffsetPx: {
|
|
182
|
+
type: Number,
|
|
183
|
+
default: 12
|
|
184
|
+
},
|
|
185
|
+
enableLogging: Boolean
|
|
186
|
+
},
|
|
187
|
+
emits: {
|
|
188
|
+
loaded: () => true,
|
|
189
|
+
error: (error) => error instanceof CapitalOSError$1
|
|
190
|
+
},
|
|
191
|
+
setup(props, { emit }) {
|
|
192
|
+
const containerRef = ref(null);
|
|
193
|
+
const { tokenData, config, invalidateToken } = useCapitalOsAuth();
|
|
194
|
+
let iframeManager = null;
|
|
195
|
+
function initializeIframe(token, container) {
|
|
196
|
+
iframeManager?.destroy();
|
|
197
|
+
const resolvedEnableLogging = props.enableLogging ?? config.enableLogging ?? false;
|
|
198
|
+
const resolvedTheme = props.theme ?? config.theme;
|
|
199
|
+
iframeManager = new IframeManager({
|
|
200
|
+
container,
|
|
201
|
+
tokenData: token,
|
|
202
|
+
renderingContext: { entryPoint: "cardsApp" },
|
|
203
|
+
theme: resolvedTheme,
|
|
204
|
+
enableLogging: resolvedEnableLogging,
|
|
205
|
+
logger: config.logger,
|
|
206
|
+
sdkVersion: version,
|
|
207
|
+
heightOffsetPx: props.heightOffsetPx,
|
|
208
|
+
callbacks: {
|
|
209
|
+
onLoad: () => {
|
|
210
|
+
emit("loaded");
|
|
211
|
+
},
|
|
212
|
+
onError: (rawError) => {
|
|
213
|
+
const capitalOsError = new CapitalOSError$1(rawError);
|
|
214
|
+
emit("error", capitalOsError);
|
|
215
|
+
},
|
|
216
|
+
onTokenExpired: () => {
|
|
217
|
+
invalidateToken();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
watch([tokenData, containerRef], ([newTokenData, container]) => {
|
|
223
|
+
if (newTokenData && container) initializeIframe(newTokenData, container);
|
|
224
|
+
}, { immediate: true });
|
|
225
|
+
onUnmounted(() => {
|
|
226
|
+
iframeManager?.destroy();
|
|
227
|
+
iframeManager = null;
|
|
228
|
+
});
|
|
229
|
+
return () => h("div", {
|
|
230
|
+
ref: (el) => {
|
|
231
|
+
containerRef.value = el;
|
|
232
|
+
},
|
|
233
|
+
class: "capitalos-iframe-container",
|
|
234
|
+
style: { width: "100%" }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
//#endregion
|
|
240
|
+
export { CAPITALOS_INJECTION_KEY, CapitalOSError, CardsApp, ErrorCode, createCapitalOs, useCapitalOsAuth };
|
|
241
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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","iframeManager: IframeManager | null","token: TokenData","container: HTMLElement","SDK_VERSION","rawError: RawErrorDetails","el: Element | null"],"sources":["../src/plugin.ts","../src/composables/use-capitalos-auth.ts","../package.json","../src/components/CardsApp.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 { 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-rc.1\",\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 },\n \"keywords\": [\n \"capitalos\",\n \"vue\",\n \"sdk\",\n \"iframe\"\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","// =============================================================================\n// MAINTAINER NOTE: Why defineComponent + render function instead of SFC?\n// =============================================================================\n//\n// This component uses `defineComponent()` with a render function (`h()`) instead\n// of a Single File Component (.vue file) for Vue 2.7/3 compatibility:\n//\n// - SFCs require different compilers for Vue 2 vs Vue 3\n// - Render functions work identically in both versions\n// - Simpler build - no vue-compiler needed, just TypeScript\n//\n// This is a deliberate architectural choice for cross-version compatibility.\n// Do NOT convert this to an SFC without understanding the implications.\n// =============================================================================\n\nimport { defineComponent, h, ref, watch, onUnmounted, type PropType } from 'vue'\nimport {\n IframeManager,\n CapitalOSError,\n type ThemeColorScheme,\n type TokenData,\n type RawErrorDetails,\n} from '@capitalos/core'\nimport { useCapitalOsAuth } from '../composables/use-capitalos-auth'\nimport { version as SDK_VERSION } from '../../package.json'\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 *\n * Note: CardsApp is not designed to be conditionally mounted/unmounted frequently.\n * For show/hide behavior, consumers should toggle visibility (e.g., v-show) rather\n * than mount/unmount repeatedly.\n */\nexport const CardsApp = defineComponent({\n name: 'CardsApp',\n props: {\n /**\n * Theme color scheme for the cards 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 emits: {\n /**\n * Emitted when the cards interface has finished loading.\n */\n loaded: () => true,\n /**\n * Emitted when an error occurs in the cards interface.\n */\n error: (error: CapitalOSError) => error instanceof CapitalOSError,\n },\n setup(props, { emit }) {\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: { entryPoint: 'cardsApp' },\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 const capitalOsError = new CapitalOSError(rawError)\n emit('error', capitalOsError)\n },\n onTokenExpired: () => {\n invalidateToken()\n },\n },\n })\n }\n\n // MAINTAINER NOTE: 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 // MAINTAINER NOTE: 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 return () =>\n 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"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capitalos/vue",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Vue SDK for CapitalOS",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"capitalos",
|
|
25
|
+
"vue",
|
|
26
|
+
"sdk",
|
|
27
|
+
"iframe"
|
|
28
|
+
],
|
|
29
|
+
"author": "CapitalOS",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vue": "^2.7.0 || ^3.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@capitalos/core": "0.1.0-rc.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"tsdown": "^0.9.2",
|
|
39
|
+
"typescript": "^5.6.2",
|
|
40
|
+
"vue": "^3.5.13",
|
|
41
|
+
"concurrently": "^9.1.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
|
+
"lint": "eslint src --ext .ts --fix",
|
|
47
|
+
"lint-ci": "eslint src --ext .ts",
|
|
48
|
+
"start": "pnpm --filter vue-test-app dev",
|
|
49
|
+
"start:all": "concurrently \"pnpm --filter sdk-test-server start\" \"pnpm start\""
|
|
50
|
+
}
|
|
51
|
+
}
|