@milaboratories/milaboratories.monetization-test.ui 1.1.83 → 1.1.84
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/assets/{index-BzrVa4ad.js → index-XiBOxqnB.js} +91 -86
- package/dist/assets/{index-BzrVa4ad.js.map → index-XiBOxqnB.js.map} +1 -1
- package/dist/index.html +1 -1
- package/eslint.config.mjs +9 -0
- package/package.json +5 -4
- package/src/app.ts +4 -4
- package/src/main.ts +1 -1
- package/src/pages/MainPage.vue +119 -84
- package/src/tokens.ts +44 -0
package/dist/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-XiBOxqnB.js"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/milaboratories.monetization-test.ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.84",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@platforma-sdk/ui-vue": "",
|
|
7
7
|
"vue": "^3.5.13",
|
|
8
8
|
"zod": "~3.23.8",
|
|
9
|
-
"@
|
|
10
|
-
"@
|
|
9
|
+
"@milaboratories/milaboratories.monetization-test.model": "1.0.7",
|
|
10
|
+
"@platforma-sdk/model": "1.42.25"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"typescript": "~5.6.3",
|
|
14
|
-
"@milaboratories/ts-builder": "1.0.
|
|
14
|
+
"@milaboratories/ts-builder": "1.0.5",
|
|
15
15
|
"@milaboratories/ts-configs": "1.0.6",
|
|
16
16
|
"@milaboratories/build-configs": "1.0.8"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"dev": "ts-builder serve --target browser",
|
|
20
|
+
"lint": "eslint .",
|
|
20
21
|
"watch": "ts-builder build --target browser --watch",
|
|
21
22
|
"build": "ts-builder build --target browser",
|
|
22
23
|
"type-check": "ts-builder types --target browser"
|
package/src/app.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { model } from
|
|
2
|
-
import { defineApp } from
|
|
3
|
-
import MainPage from
|
|
1
|
+
import { model } from '@milaboratories/milaboratories.monetization-test.model';
|
|
2
|
+
import { defineApp } from '@platforma-sdk/ui-vue';
|
|
3
|
+
import MainPage from './pages/MainPage.vue';
|
|
4
4
|
|
|
5
5
|
export const sdkPlugin = defineApp(model, () => {
|
|
6
6
|
return {
|
|
7
7
|
routes: {
|
|
8
|
-
|
|
8
|
+
'/': () => MainPage,
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
11
|
});
|
package/src/main.ts
CHANGED
package/src/pages/MainPage.vue
CHANGED
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ImportFileHandle } from '@platforma-sdk/model';
|
|
3
|
+
import { getFileNameFromHandle } from '@platforma-sdk/model';
|
|
3
4
|
import type { ImportedFiles, ListOption } from '@platforma-sdk/ui-vue';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import {
|
|
6
|
+
PlDialogModal,
|
|
7
|
+
PlLogView,
|
|
8
|
+
PlAlert,
|
|
9
|
+
PlBlockPage,
|
|
10
|
+
PlBtnPrimary,
|
|
11
|
+
PlCheckbox,
|
|
12
|
+
PlDropdownMulti,
|
|
13
|
+
PlFileDialog,
|
|
14
|
+
PlFileInput,
|
|
15
|
+
PlRow,
|
|
16
|
+
PlTextField,
|
|
17
|
+
PlDropdown,
|
|
18
|
+
PlContainer,
|
|
19
|
+
} from '@platforma-sdk/ui-vue';
|
|
6
20
|
import { useApp } from '../app';
|
|
7
|
-
import {
|
|
21
|
+
import { reactive, ref, watch } from 'vue';
|
|
22
|
+
import { parseToken, verify } from '../tokens';
|
|
8
23
|
|
|
9
24
|
const app = useApp();
|
|
10
25
|
|
|
26
|
+
const PRODUCT_KEY_PREFIX = 'PRODUCT:';
|
|
27
|
+
const PRODUCT_KEY_LENGTH = 48;
|
|
28
|
+
|
|
29
|
+
function extractProductKey(key: string) {
|
|
30
|
+
if (key.startsWith(PRODUCT_KEY_PREFIX)) {
|
|
31
|
+
key = key.slice(PRODUCT_KEY_PREFIX.length);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (key.length !== PRODUCT_KEY_LENGTH) {
|
|
35
|
+
throw new Error('Invalid product key');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
const dropdownOptions: ListOption<string>[] = [
|
|
12
42
|
{
|
|
13
43
|
text: 'sha256',
|
|
14
|
-
value: 'sha256'
|
|
44
|
+
value: 'sha256',
|
|
15
45
|
},
|
|
16
46
|
{
|
|
17
47
|
text: 'lines (only in .zip files)',
|
|
@@ -19,15 +49,15 @@ const dropdownOptions: ListOption<string>[] = [
|
|
|
19
49
|
},
|
|
20
50
|
{
|
|
21
51
|
text: 'size',
|
|
22
|
-
value: 'size'
|
|
23
|
-
}
|
|
52
|
+
value: 'size',
|
|
53
|
+
},
|
|
24
54
|
];
|
|
25
55
|
|
|
26
56
|
const files = reactive<{
|
|
27
57
|
isMultiDialogFileOpen: boolean;
|
|
28
58
|
}>({
|
|
29
59
|
isMultiDialogFileOpen: false,
|
|
30
|
-
})
|
|
60
|
+
});
|
|
31
61
|
|
|
32
62
|
const updateHandle = (v: ImportFileHandle | undefined, i: number) => {
|
|
33
63
|
if (v) {
|
|
@@ -40,108 +70,113 @@ const updateHandle = (v: ImportFileHandle | undefined, i: number) => {
|
|
|
40
70
|
const onImport = (imported: ImportedFiles) => {
|
|
41
71
|
app.model.args.inputHandles = imported.files.map((h, i) => ({
|
|
42
72
|
handle: h,
|
|
43
|
-
fileName:
|
|
73
|
+
fileName: getFileNameFromHandle(h),
|
|
44
74
|
argName: `arg_${i}`,
|
|
45
|
-
options: ['size', 'sha256']
|
|
75
|
+
options: ['size', 'sha256'],
|
|
46
76
|
}));
|
|
47
77
|
};
|
|
48
78
|
|
|
49
|
-
const parsedToken = computed({
|
|
50
|
-
get: () => {
|
|
51
|
-
const splitted = app.model.outputs.token?.split('.') ?? [];
|
|
52
|
-
const data = splitted[1];
|
|
53
|
-
return JSON.parse(atob(data));
|
|
54
|
-
},
|
|
55
|
-
set: () => {}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
79
|
const verificationResult = ref('');
|
|
59
80
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
else
|
|
91
|
-
verificationResult.value = "Signature is incorrect";
|
|
92
|
-
} catch (e: unknown) {
|
|
93
|
-
verificationResult.value = "Verification failed";
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
const isDialogFileOpen = ref(false);
|
|
82
|
+
|
|
83
|
+
const isTokenDialogOpen = ref(false);
|
|
84
|
+
|
|
85
|
+
const tokensResult = ref<string>('');
|
|
86
|
+
|
|
87
|
+
watch(() => app.model.outputs.tokens, async (tokens) => {
|
|
88
|
+
tokensResult.value = (await Promise.all(tokens?.map(async (t) => {
|
|
89
|
+
if (!t.value) {
|
|
90
|
+
return 'token is empty';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await verify(t.value);
|
|
94
|
+
return `token: ${t.value}\nresult: ${result}\n${JSON.stringify(parseToken(t.value), null, 2)}\n\n`;
|
|
95
|
+
}) ?? [])).join('\n') ?? '';
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const productOptions = [{
|
|
99
|
+
label: 'Rabbit (no limits)',
|
|
100
|
+
value: 'PRODUCT:YAGRKKGRBYLCGDLCDVYINUSHYWGYWXWHGIINXYBQBZKMSIRC',
|
|
101
|
+
}, {
|
|
102
|
+
label: 'Crow (limit 10GB monthly)',
|
|
103
|
+
value: 'PRODUCT:JLVOZAOIOBZMLCIWQKUYZWLBAEPDHUJPHRHYAOBPDGWPVTJC',
|
|
104
|
+
}, {
|
|
105
|
+
label: 'Behemoth (1000 runs, 100GB monthly)',
|
|
106
|
+
value: 'PRODUCT:ZHJBTZESZONNVEFPGWWPDYESVYGXQOOSHYVUBWDXUHSILLDH',
|
|
107
|
+
}, {
|
|
108
|
+
label: 'Kolibri (100 runs)',
|
|
109
|
+
value: 'PRODUCT:EBVGZXPBYZLGKQHLFDVCCVFRLKTZZTJSWMNJGXNHVTMKNSPA',
|
|
110
|
+
}];
|
|
98
111
|
</script>
|
|
99
112
|
|
|
100
113
|
<template>
|
|
101
114
|
<PlBlockPage>
|
|
102
|
-
|
|
103
115
|
<template #title>
|
|
104
|
-
Monetization
|
|
116
|
+
Monetization test
|
|
105
117
|
</template>
|
|
106
118
|
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
<PlRow>
|
|
120
|
+
<PlContainer width="400px">
|
|
121
|
+
<PlDropdown v-model="app.model.args.productKey" label="Select product" :options="productOptions" />
|
|
122
|
+
<PlTextField
|
|
123
|
+
v-model="app.model.args.productKey"
|
|
124
|
+
label="or enter product key"
|
|
125
|
+
clearable
|
|
126
|
+
:parse="extractProductKey"
|
|
127
|
+
/>
|
|
128
|
+
</PlContainer>
|
|
129
|
+
</PlRow>
|
|
130
|
+
|
|
131
|
+
<PlCheckbox v-model="app.model.args.shouldAddRunPerFile"> Add run per file </PlCheckbox>
|
|
132
|
+
|
|
133
|
+
<PlRow width="400px">
|
|
113
134
|
<PlBtnPrimary @click="files.isMultiDialogFileOpen = true">
|
|
114
135
|
Open multiple files to monetize
|
|
115
136
|
</PlBtnPrimary>
|
|
116
|
-
|
|
137
|
+
<PlBtnPrimary :disabled="!app.model.outputs['__mnzInfo']" @click="isDialogFileOpen = true">
|
|
138
|
+
Show mnz info
|
|
139
|
+
</PlBtnPrimary>
|
|
140
|
+
</PlRow>
|
|
117
141
|
<template v-for="({ handle }, i) of app.model.args.inputHandles" :key="i">
|
|
118
142
|
<PlRow>
|
|
119
143
|
<PlTextField v-model="app.model.args.inputHandles[i].fileName" label="Type file name" />
|
|
120
144
|
<PlTextField v-model="app.model.args.inputHandles[i].argName" label="Type argument name" />
|
|
121
|
-
<PlFileInput
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
145
|
+
<PlFileInput
|
|
146
|
+
:model-value="handle"
|
|
147
|
+
@update:model-value="(v: ImportFileHandle | undefined) => updateHandle(v, i)"
|
|
148
|
+
/>
|
|
149
|
+
<PlDropdownMulti
|
|
150
|
+
v-model="app.model.args.inputHandles[i].options" label="Metrics to monetize"
|
|
151
|
+
:options="dropdownOptions"
|
|
152
|
+
/>
|
|
125
153
|
</PlRow>
|
|
126
154
|
</template>
|
|
127
|
-
<PlFileDialog v-model="files.isMultiDialogFileOpen" multi @import:files="onImport" />
|
|
128
|
-
|
|
129
|
-
<PlContainer />
|
|
130
|
-
|
|
131
|
-
<pre> {{ app.model.outputs['__mnzInfo'] }} </pre>
|
|
132
155
|
|
|
133
|
-
<
|
|
134
|
-
|
|
156
|
+
<PlRow>
|
|
157
|
+
<PlBtnPrimary @click="isTokenDialogOpen = true">
|
|
158
|
+
Show tokens
|
|
159
|
+
</PlBtnPrimary>
|
|
160
|
+
</PlRow>
|
|
135
161
|
|
|
136
|
-
<
|
|
137
|
-
|
|
162
|
+
<PlAlert v-if="verificationResult" label="token verification"> {{ verificationResult }}</PlAlert>
|
|
163
|
+
</PlBlockPage>
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
</PlAlert>
|
|
165
|
+
<PlFileDialog v-model="files.isMultiDialogFileOpen" multi @import:files="onImport" />
|
|
141
166
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
<PlDialogModal v-model="isDialogFileOpen" size="medium">
|
|
168
|
+
<template #title>
|
|
169
|
+
Monetization info
|
|
170
|
+
</template>
|
|
171
|
+
<PlLogView :value="JSON.stringify(app.model.outputs['__mnzInfo'], null, 2)" />
|
|
172
|
+
</PlDialogModal>
|
|
145
173
|
|
|
146
|
-
|
|
174
|
+
<PlDialogModal v-model="isTokenDialogOpen" size="medium">
|
|
175
|
+
<template #title>
|
|
176
|
+
Tokens
|
|
177
|
+
</template>
|
|
178
|
+
<PlLogView
|
|
179
|
+
:value="tokensResult"
|
|
180
|
+
/>
|
|
181
|
+
</PlDialogModal>
|
|
147
182
|
</template>
|
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const publicKey = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECGShTw8Plag1uMuCg9OMYVHCF+wzjvXKr3cihyO77jEe9CrF6RP9tfnCd2XjM7XqQ0QH3i41rz5ohCB9fDDBbQ==';
|
|
2
|
+
|
|
3
|
+
const splitAndValidateToken = (token: string): [string, string, string] => {
|
|
4
|
+
const [base64Header, base64Payload, signature] = token.split('.');
|
|
5
|
+
if (!base64Header || typeof base64Payload !== 'string' || typeof signature !== 'string')
|
|
6
|
+
throw new Error('Invalid token body');
|
|
7
|
+
return [base64Header, base64Payload, signature];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const parseToken = (token: string) => {
|
|
11
|
+
const [, base64Payload] = splitAndValidateToken(token);
|
|
12
|
+
return JSON.parse(atob(base64Payload));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function verify(token: string) {
|
|
16
|
+
const cryptoPublicKey = await crypto.subtle.importKey(
|
|
17
|
+
'spki',
|
|
18
|
+
Uint8Array.from(atob(publicKey), (c) => c.charCodeAt(0)).buffer,
|
|
19
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
20
|
+
true,
|
|
21
|
+
['verify'],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const [base64Header, base64Payload, signature] = splitAndValidateToken(token);
|
|
25
|
+
|
|
26
|
+
const signatureBinary = Uint8Array.from(
|
|
27
|
+
atob(signature.replace(/-/g, '+').replace(/_/g, '/')),
|
|
28
|
+
(c) => c.charCodeAt(0),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = await crypto.subtle.verify(
|
|
33
|
+
{ name: 'ECDSA', hash: { name: 'SHA-256' } },
|
|
34
|
+
cryptoPublicKey,
|
|
35
|
+
signatureBinary,
|
|
36
|
+
new TextEncoder().encode(`${base64Header}.${base64Payload}`),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (result) return 'Signature is correct';
|
|
40
|
+
else return 'Signature is incorrect';
|
|
41
|
+
} catch (_e: unknown) {
|
|
42
|
+
return 'Verification failed';
|
|
43
|
+
}
|
|
44
|
+
}
|