@milaboratories/milaboratories.monetization-test.ui 1.1.83 → 1.1.85

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.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-BzrVa4ad.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-CCSGnWKW.js"></script>
8
8
  </head>
9
9
  <body>
10
10
  <div id="app"></div>
@@ -0,0 +1,9 @@
1
+ import { ui } from '@platforma-sdk/eslint-config';
2
+
3
+ /** @type {import('eslint').Linter.Config[]} */
4
+ export default [...ui, {
5
+ files: ['src/pages/DraftsPage.vue'],
6
+ rules: {
7
+ '@stylistic/quotes': 'off' // overriding example
8
+ }
9
+ }];
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "@milaboratories/milaboratories.monetization-test.ui",
3
- "version": "1.1.83",
3
+ "version": "1.1.85",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@platforma-sdk/ui-vue": "",
7
6
  "vue": "^3.5.13",
8
7
  "zod": "~3.23.8",
9
8
  "@platforma-sdk/model": "1.42.25",
10
- "@milaboratories/milaboratories.monetization-test.model": "1.0.6"
9
+ "@milaboratories/milaboratories.monetization-test.model": "1.0.7",
10
+ "@platforma-sdk/ui-vue": "1.42.34"
11
11
  },
12
12
  "devDependencies": {
13
13
  "typescript": "~5.6.3",
14
- "@milaboratories/ts-builder": "1.0.4",
15
14
  "@milaboratories/ts-configs": "1.0.6",
15
+ "@milaboratories/ts-builder": "1.0.5",
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 "@milaboratories/milaboratories.monetization-test.model";
2
- import { defineApp } from "@platforma-sdk/ui-vue";
3
- import MainPage from "./pages/MainPage.vue";
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
- "/": () => MainPage,
8
+ '/': () => MainPage,
9
9
  },
10
10
  };
11
11
  });
package/src/main.ts CHANGED
@@ -2,4 +2,4 @@ import { BlockLayout } from '@platforma-sdk/ui-vue';
2
2
  import { createApp } from 'vue';
3
3
  import { sdkPlugin } from './app';
4
4
 
5
- createApp(BlockLayout).use(sdkPlugin).mount("#app");
5
+ createApp(BlockLayout).use(sdkPlugin).mount('#app');
@@ -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
- import { PlCheckbox, PlAlert, PlBlockPage, PlContainer, PlRow, PlTextField, PlBtnPrimary, PlFileDialog, PlFileInput, PlDropdownMulti } from '@platforma-sdk/ui-vue';
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 { computed, reactive, ref } from 'vue';
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: `test${i}.txt`,
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 publicKey = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECGShTw8Plag1uMuCg9OMYVHCF+wzjvXKr3cihyO77jEe9CrF6RP9tfnCd2XjM7XqQ0QH3i41rz5ohCB9fDDBbQ==';
61
-
62
- async function verify(token: string) {
63
- const cryptoPublicKey = await crypto.subtle.importKey(
64
- 'spki',
65
- Uint8Array.from(atob(publicKey), c => c.charCodeAt(0)).buffer,
66
- { name: 'ECDSA', namedCurve: 'P-256' },
67
- true,
68
- ['verify']
69
- );
70
-
71
- const [base64Header, base64Payload, signature] = token.split('.');
72
- if (!base64Header || typeof base64Payload !== 'string' || typeof signature !== 'string')
73
- throw new Error('Invalid token body');
74
-
75
- const signatureBinary = Uint8Array.from(
76
- atob(signature.replace(/-/g, '+').replace(/_/g, '/')),
77
- c => c.charCodeAt(0)
78
- );
79
-
80
- try {
81
- const result = await crypto.subtle.verify(
82
- { name: 'ECDSA', hash: { name: 'SHA-256' } },
83
- cryptoPublicKey,
84
- signatureBinary,
85
- new TextEncoder().encode(`${base64Header}.${base64Payload}`)
86
- );
87
-
88
- if (result)
89
- verificationResult.value = "Signature is correct";
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 example
116
+ Monetization test
105
117
  </template>
106
118
 
107
- <PlTextField v-model="app.model.args.productKey"
108
- label="Enter product key (keep MIFAKEMIFAKEMIFAKE for fake product)" clearable />
109
-
110
- <PlCheckbox v-model="app.model.args.shouldAddRunPerFile" > Add run per file </PlCheckbox>
111
-
112
- <PlContainer width="400px">
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
- </PlContainer>
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 :model-value="handle"
122
- @update:model-value="(v: ImportFileHandle | undefined) => updateHandle(v, i)" />
123
- <PlDropdownMulti label="Metrics to monetize" v-model="app.model.args.inputHandles[i].options"
124
- :options="dropdownOptions" />
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
- <PlAlert label="token" v-if="app.model.outputs.token"> {{ app.model.outputs.token }}
134
- </PlAlert>
156
+ <PlRow>
157
+ <PlBtnPrimary @click="isTokenDialogOpen = true">
158
+ Show tokens
159
+ </PlBtnPrimary>
160
+ </PlRow>
135
161
 
136
- <PlBtnPrimary v-if="app.model.outputs.token" @click="verify(app.model.outputs.token)">
137
- Verify</PlBtnPrimary>
162
+ <PlAlert v-if="verificationResult" label="token verification"> {{ verificationResult }}</PlAlert>
163
+ </PlBlockPage>
138
164
 
139
- <PlAlert label="token verification" v-if="verificationResult"> {{ verificationResult }}
140
- </PlAlert>
165
+ <PlFileDialog v-model="files.isMultiDialogFileOpen" multi @import:files="onImport" />
141
166
 
142
- <PlAlert label="trying to parse a token" v-if="app.model.outputs.token">
143
- <pre> {{ JSON.stringify(parsedToken, null, 2) }} </pre>
144
- </PlAlert>
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
- </PlBlockPage>
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
+ }