@bitmagic/asset-core 0.2.33-dev.0 → 0.2.33

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.
@@ -0,0 +1,299 @@
1
+ /**
2
+ * The Game Exchange: cross-promotion between published Bitmagic games (docs/game-exchange.md).
3
+ *
4
+ * One source of truth for the two ends that can import it. The CLI reads a project's declaration
5
+ * (`worldProfileData.gameExchange` in world.json) through `parseGameExchangeDeclaration` before it
6
+ * publishes, and api-server parses the same value again when it arrives, so a hand-edited or stale
7
+ * declaration is refused in the same words. The wire types below are the bodies both ends exchange.
8
+ * The engine cannot import this package: it repeats the few ids it needs by hand.
9
+ *
10
+ * A game SHOWS tiles (it earns exchange credits) and is LISTED (it may be shown in other games,
11
+ * which spends them). Amounts are integer milli-credits: 1 exchange credit = 1000.
12
+ *
13
+ * Pure: no filesystem, no network.
14
+ */
15
+ /**
16
+ * Where a tile can appear. `custom` is wherever the game mounts it through `engine.gameExchange`;
17
+ * `world` is an ad the game draws into its own scene (a poster, a TV) and opens in the engine's
18
+ * popup when the player clicks it.
19
+ */
20
+ export type GameExchangePlacement = 'end' | 'pause' | 'custom' | 'world';
21
+ /** The placements a project can turn on in world.json; `custom` and `world` need code, not a declaration. */
22
+ export type GameExchangeDeclarablePlacement = 'end' | 'pause';
23
+ export type GameExchangeFormat = 'banner' | 'video';
24
+ export declare const GAME_EXCHANGE_PLACEMENTS: readonly GameExchangePlacement[];
25
+ export declare const GAME_EXCHANGE_DECLARABLE_PLACEMENTS: readonly GameExchangeDeclarablePlacement[];
26
+ export declare const GAME_EXCHANGE_FORMATS: readonly GameExchangeFormat[];
27
+ export declare const GAME_EXCHANGE_DEFAULT_PLACEMENTS: readonly GameExchangeDeclarablePlacement[];
28
+ export declare const GAME_EXCHANGE_DEFAULT_FORMATS: readonly GameExchangeFormat[];
29
+ /** The most ads one serve request may ask for (`count`); a larger count is clamped to it. */
30
+ export declare const GAME_EXCHANGE_MAX_ADS_PER_REQUEST = 6;
31
+ /**
32
+ * The widest copy of a still that a `world` ad (a poster in a game's own scene) is drawn from:
33
+ * the cover's 768 px copy at most, never the full image. A game without such a copy is not
34
+ * served for `world`.
35
+ */
36
+ export declare const GAME_EXCHANGE_WORLD_IMAGE_MAX_WIDTH = 768;
37
+ /** The version of the game-called request bodies (`v`). */
38
+ export declare const GAME_EXCHANGE_WIRE_VERSION = 1;
39
+ /** The landing page's query parameter carrying the click id: `?gx=<clickId>&src=exchange`. */
40
+ export declare const GAME_EXCHANGE_CLICK_PARAM = "gx";
41
+ /** The `src` value beside it. */
42
+ export declare const GAME_EXCHANGE_CLICK_SOURCE = "exchange";
43
+ /** A click id: `gxc_` and 16 lowercase letters or digits. */
44
+ export declare const GAME_EXCHANGE_CLICK_ID_PATTERN: RegExp;
45
+ export declare function isExchangeClickId(value: unknown): value is string;
46
+ export declare function isGameExchangePlacement(value: unknown): value is GameExchangePlacement;
47
+ export declare function isGameExchangeDeclarablePlacement(value: unknown): value is GameExchangeDeclarablePlacement;
48
+ export declare function isGameExchangeFormat(value: unknown): value is GameExchangeFormat;
49
+ /**
50
+ * A declaration with every default filled in: what publish sends and what an enrolment stores.
51
+ * `enabled` = this game shows tiles; `listed` = it may be shown in other games; `houseOnly` = it
52
+ * shows only Bitmagic's picks.
53
+ */
54
+ export interface GameExchangeDeclaration {
55
+ enabled: boolean;
56
+ listed: boolean;
57
+ houseOnly: boolean;
58
+ placements: GameExchangeDeclarablePlacement[];
59
+ formats: GameExchangeFormat[];
60
+ }
61
+ export type GameExchangeDeclarationResult = {
62
+ ok: true;
63
+ value: GameExchangeDeclaration;
64
+ } | {
65
+ ok: false;
66
+ error: string;
67
+ };
68
+ /**
69
+ * The declaration with its defaults filled in, or one sentence saying why it is not one. `listed`
70
+ * defaults to `enabled`, `houseOnly` to false, `placements` to the end screen and `formats` to
71
+ * both. Nothing is guessed: a wrong type, an unknown id or an unknown key is refused, because a
72
+ * misread declaration would enrol (or un-enrol) a live game.
73
+ */
74
+ export declare function parseGameExchangeDeclaration(raw: unknown): GameExchangeDeclarationResult;
75
+ export interface ExchangeDevice {
76
+ mobile: boolean;
77
+ width: number;
78
+ height: number;
79
+ /** The game runs inside a frame (the portal's play page), not as the top window. */
80
+ framed: boolean;
81
+ }
82
+ /** `POST /api/play/exchange/serve`. */
83
+ export interface ExchangeServeRequest {
84
+ v: typeof GAME_EXCHANGE_WIRE_VERSION;
85
+ /** The engine's card revision. */
86
+ cv: number;
87
+ /** The engine version, when the build knows it. */
88
+ ev?: string;
89
+ gameId: string;
90
+ placement: GameExchangePlacement;
91
+ formats: GameExchangeFormat[];
92
+ device: ExchangeDevice;
93
+ /** Listed games this player saw recently; a hint, at most five are honoured. */
94
+ seen?: string[];
95
+ /**
96
+ * How many different games to answer with: 1 when absent, anything else is clamped to
97
+ * 1..GAME_EXCHANGE_MAX_ADS_PER_REQUEST. Each ad carries its own token.
98
+ */
99
+ count?: number;
100
+ }
101
+ export interface ExchangeImageSource {
102
+ url: string;
103
+ width: number;
104
+ }
105
+ export interface ExchangeImage {
106
+ url: string;
107
+ width: number;
108
+ height: number;
109
+ sources: ExchangeImageSource[];
110
+ /** The box that must stay in view when the image is cropped, as fractions of its size. */
111
+ focus?: {
112
+ x0: number;
113
+ y0: number;
114
+ x1: number;
115
+ y1: number;
116
+ };
117
+ }
118
+ export interface ExchangeVideo {
119
+ url: string;
120
+ width: number;
121
+ height: number;
122
+ durationMs: number;
123
+ }
124
+ /** `exchange` = another creator's listed game; `house` = one of Bitmagic's picks. */
125
+ export type ExchangeAdKind = 'exchange' | 'house';
126
+ export type ExchangeOpenIn = 'auto' | 'new' | 'same';
127
+ export interface ExchangeAd {
128
+ token: string;
129
+ kind: ExchangeAdKind;
130
+ gameId: string;
131
+ title: string;
132
+ tagline?: string;
133
+ banner: ExchangeImage;
134
+ square?: ExchangeImage;
135
+ /** The tile's clip (512×288), for the tile and the popup. */
136
+ video?: ExchangeVideo;
137
+ /**
138
+ * Only on `world` answers: the small clip (320×180) for a screen in the game's own scene. A
139
+ * texture is never made from `video`.
140
+ */
141
+ worldVideo?: ExchangeVideo;
142
+ clickUrl: string;
143
+ label: string;
144
+ cta: string;
145
+ disclosure: string;
146
+ openIn: ExchangeOpenIn;
147
+ videoLoops: number;
148
+ }
149
+ /**
150
+ * Every failure answers `{ enabled: false }` with status 200; `ads` holds up to the request's
151
+ * `count` ads (one when it asked for none), each a different game. `expiresInMs` is how long its
152
+ * tokens are good for, which depends on the placement (`world` tokens live much longer).
153
+ */
154
+ export type ExchangeServeResponse = {
155
+ enabled: false;
156
+ } | {
157
+ enabled: true;
158
+ minViewMs: number;
159
+ armMs: number;
160
+ expiresInMs: number;
161
+ ads: ExchangeAd[];
162
+ };
163
+ /** `POST /api/play/exchange/impression`; answers 204. */
164
+ export interface ExchangeImpressionRequest {
165
+ v: typeof GAME_EXCHANGE_WIRE_VERSION;
166
+ token: string;
167
+ visibleMs: number;
168
+ }
169
+ export type ExchangeClickStep = 'visit' | 'play' | 'engaged';
170
+ export declare const EXCHANGE_CLICK_STEPS: readonly ExchangeClickStep[];
171
+ export interface ExchangeClickStepRequest {
172
+ visitorKey: string;
173
+ framed?: boolean;
174
+ }
175
+ export type ExchangeEnrollmentSource = 'publish' | 'cli' | 'admin';
176
+ export type ExchangeSuitability = 'unreviewed' | 'all_audiences' | 'restricted';
177
+ /** `pending` = a build is queued or running; the rest are the stored creative's status. */
178
+ export type ExchangeVideoStatus = 'ready' | 'none' | 'failed' | 'pending';
179
+ export interface ExchangeEnrollment {
180
+ shows: boolean;
181
+ listed: boolean;
182
+ houseOnly: boolean;
183
+ placements: GameExchangeDeclarablePlacement[];
184
+ formats: GameExchangeFormat[];
185
+ source: ExchangeEnrollmentSource;
186
+ /** Epoch milliseconds. */
187
+ updatedAt: number;
188
+ blocked: boolean;
189
+ blockedNote?: string;
190
+ suitability: ExchangeSuitability;
191
+ }
192
+ /** The creator's wallet, shared by all of their games. */
193
+ export interface ExchangeWallet {
194
+ balanceMilli: number;
195
+ earnedMilli: number;
196
+ spentMilli: number;
197
+ grantedMilli: number;
198
+ frozen: boolean;
199
+ /** How many of the creator's games are enrolled. */
200
+ games: number;
201
+ }
202
+ export interface ExchangeRates {
203
+ impressionCostMilli: number;
204
+ clickCostMilli: number;
205
+ /** This game's earn rate, in percent of the impression cost. */
206
+ earnPct: number;
207
+ earnPctBase: number;
208
+ /** 0..1 among games that show tiles; null until the game has a score. */
209
+ publisherPercentile: number | null;
210
+ starterGrantMilli: number;
211
+ starterGranted: boolean;
212
+ /** The balance below which the creator's games stop being listed. */
213
+ minBalanceMilli: number;
214
+ }
215
+ /** What this game did as the one showing tiles. */
216
+ export interface ExchangeShownStats {
217
+ requests: number;
218
+ filled: number;
219
+ impressions: number;
220
+ credited: number;
221
+ uncredited: number;
222
+ clicks: number;
223
+ earnedMilli: number;
224
+ }
225
+ /** What this game got as the one listed in other games. */
226
+ export interface ExchangeAdvertisedStats {
227
+ impressions: number;
228
+ charged: number;
229
+ unfunded: number;
230
+ house: number;
231
+ clicks: number;
232
+ visits: number;
233
+ plays: number;
234
+ engaged: number;
235
+ spentMilli: number;
236
+ }
237
+ export interface ExchangeStats {
238
+ windowDays: number;
239
+ shown: ExchangeShownStats;
240
+ advertised: ExchangeAdvertisedStats;
241
+ uncreditedByReason: Record<string, number>;
242
+ }
243
+ export interface ExchangeCreativeStatus {
244
+ banner: boolean;
245
+ video: ExchangeVideoStatus;
246
+ videoUrl?: string;
247
+ }
248
+ export interface ExchangeEligibility {
249
+ canShow: boolean;
250
+ canBeListed: boolean;
251
+ /** Why not, as sentences; empty when both are true. */
252
+ reasons: string[];
253
+ }
254
+ /** `GET /api/cli/v1/exchange/status?gameId=`. Answers even when the feature is off or the game was never enrolled. */
255
+ export interface ExchangeStatus {
256
+ success: true;
257
+ /** False when the Game Exchange is switched off in this environment. */
258
+ available: boolean;
259
+ enrollment: ExchangeEnrollment | null;
260
+ wallet: ExchangeWallet;
261
+ rates: ExchangeRates;
262
+ stats: ExchangeStats;
263
+ creative: ExchangeCreativeStatus;
264
+ eligibility: ExchangeEligibility;
265
+ }
266
+ /** `POST /api/cli/v1/exchange/settings`. `enabled` = shows tiles; an absent key keeps its value. */
267
+ export interface ExchangeSettingsRequest {
268
+ gameId: string;
269
+ enabled?: boolean;
270
+ listed?: boolean;
271
+ houseOnly?: boolean;
272
+ placements?: GameExchangeDeclarablePlacement[];
273
+ formats?: GameExchangeFormat[];
274
+ }
275
+ export interface ExchangeSettingsResponse {
276
+ success: true;
277
+ enrollment: ExchangeEnrollment;
278
+ }
279
+ /** The codes the exchange's own CLI routes refuse with; a Pro refusal carries the lane's usual code. */
280
+ export type ExchangeErrorCode = 'exchange_unavailable' | 'game_not_found' | 'not_owner';
281
+ export interface ExchangeErrorResponse {
282
+ success: false;
283
+ code: string;
284
+ error: string;
285
+ }
286
+ /**
287
+ * The optional `gameExchange` body key: the declaration enrols the game, `null` says the project
288
+ * declares nothing (an enrolment made by publish or the CLI is switched off), and an absent key
289
+ * keeps whatever is there.
290
+ */
291
+ export type PublishGameExchangeRequest = GameExchangeDeclaration | null;
292
+ /** The `gameExchange` key of the response, present when the publish touched the enrolment. */
293
+ export interface PublishGameExchangeResult {
294
+ enrolled: boolean;
295
+ shows: boolean;
296
+ listed: boolean;
297
+ balanceMilli: number;
298
+ }
299
+ //# sourceMappingURL=game-exchange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-exchange.d.ts","sourceRoot":"","sources":["../src/game-exchange.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The Game Exchange: cross-promotion between published Bitmagic games (docs/game-exchange.md).
3
+ *
4
+ * One source of truth for the two ends that can import it. The CLI reads a project's declaration
5
+ * (`worldProfileData.gameExchange` in world.json) through `parseGameExchangeDeclaration` before it
6
+ * publishes, and api-server parses the same value again when it arrives, so a hand-edited or stale
7
+ * declaration is refused in the same words. The wire types below are the bodies both ends exchange.
8
+ * The engine cannot import this package: it repeats the few ids it needs by hand.
9
+ *
10
+ * A game SHOWS tiles (it earns exchange credits) and is LISTED (it may be shown in other games,
11
+ * which spends them). Amounts are integer milli-credits: 1 exchange credit = 1000.
12
+ *
13
+ * Pure: no filesystem, no network.
14
+ */
15
+ export const GAME_EXCHANGE_PLACEMENTS = ['end', 'pause', 'custom', 'world'];
16
+ export const GAME_EXCHANGE_DECLARABLE_PLACEMENTS = ['end', 'pause'];
17
+ export const GAME_EXCHANGE_FORMATS = ['banner', 'video'];
18
+ export const GAME_EXCHANGE_DEFAULT_PLACEMENTS = ['end'];
19
+ export const GAME_EXCHANGE_DEFAULT_FORMATS = ['banner', 'video'];
20
+ /** The most ads one serve request may ask for (`count`); a larger count is clamped to it. */
21
+ export const GAME_EXCHANGE_MAX_ADS_PER_REQUEST = 6;
22
+ /**
23
+ * The widest copy of a still that a `world` ad (a poster in a game's own scene) is drawn from:
24
+ * the cover's 768 px copy at most, never the full image. A game without such a copy is not
25
+ * served for `world`.
26
+ */
27
+ export const GAME_EXCHANGE_WORLD_IMAGE_MAX_WIDTH = 768;
28
+ /** The version of the game-called request bodies (`v`). */
29
+ export const GAME_EXCHANGE_WIRE_VERSION = 1;
30
+ /** The landing page's query parameter carrying the click id: `?gx=<clickId>&src=exchange`. */
31
+ export const GAME_EXCHANGE_CLICK_PARAM = 'gx';
32
+ /** The `src` value beside it. */
33
+ export const GAME_EXCHANGE_CLICK_SOURCE = 'exchange';
34
+ /** A click id: `gxc_` and 16 lowercase letters or digits. */
35
+ export const GAME_EXCHANGE_CLICK_ID_PATTERN = /^gxc_[a-z0-9]{16}$/;
36
+ export function isExchangeClickId(value) {
37
+ return typeof value === 'string' && GAME_EXCHANGE_CLICK_ID_PATTERN.test(value);
38
+ }
39
+ export function isGameExchangePlacement(value) {
40
+ return GAME_EXCHANGE_PLACEMENTS.includes(value);
41
+ }
42
+ export function isGameExchangeDeclarablePlacement(value) {
43
+ return GAME_EXCHANGE_DECLARABLE_PLACEMENTS.includes(value);
44
+ }
45
+ export function isGameExchangeFormat(value) {
46
+ return GAME_EXCHANGE_FORMATS.includes(value);
47
+ }
48
+ const DECLARATION_KEYS = ['enabled', 'listed', 'houseOnly', 'placements', 'formats'];
49
+ function isRecord(value) {
50
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ }
52
+ function quoted(ids) {
53
+ return ids.map((id) => `"${id}"`).join(', ');
54
+ }
55
+ /**
56
+ * A list of ids from a declaration: the default when the key is absent, otherwise the ids without
57
+ * repeats and in the spec's order, or the sentence saying what is wrong with it.
58
+ */
59
+ function parseIdList(value, key, allowed, fallback) {
60
+ if (value === undefined)
61
+ return [...fallback];
62
+ if (!Array.isArray(value))
63
+ return `"${key}" must be a list of ${quoted(allowed)}.`;
64
+ if (value.length === 0)
65
+ return `"${key}" must not be empty; leave it out for the default.`;
66
+ for (const item of value) {
67
+ if (!allowed.includes(item)) {
68
+ const shown = typeof item === 'string' ? `"${item}"` : JSON.stringify(item) ?? String(item);
69
+ return `"${key}" has ${shown}, which is not one of ${quoted(allowed)}.`;
70
+ }
71
+ }
72
+ return allowed.filter((id) => value.includes(id));
73
+ }
74
+ function parseFlag(value, key, fallback) {
75
+ if (value === undefined)
76
+ return fallback;
77
+ if (typeof value !== 'boolean')
78
+ return `"${key}" must be true or false.`;
79
+ return value;
80
+ }
81
+ /**
82
+ * The declaration with its defaults filled in, or one sentence saying why it is not one. `listed`
83
+ * defaults to `enabled`, `houseOnly` to false, `placements` to the end screen and `formats` to
84
+ * both. Nothing is guessed: a wrong type, an unknown id or an unknown key is refused, because a
85
+ * misread declaration would enrol (or un-enrol) a live game.
86
+ */
87
+ export function parseGameExchangeDeclaration(raw) {
88
+ if (!isRecord(raw))
89
+ return { ok: false, error: 'The gameExchange declaration must be an object.' };
90
+ const unknownKey = Object.keys(raw).find((key) => !DECLARATION_KEYS.includes(key));
91
+ if (unknownKey !== undefined) {
92
+ return { ok: false, error: `The gameExchange declaration has an unknown key "${unknownKey}"; it takes ${quoted(DECLARATION_KEYS)}.` };
93
+ }
94
+ if (typeof raw.enabled !== 'boolean') {
95
+ return { ok: false, error: 'The gameExchange declaration needs "enabled": true or false.' };
96
+ }
97
+ const enabled = raw.enabled;
98
+ const listed = parseFlag(raw.listed, 'listed', enabled);
99
+ const houseOnly = parseFlag(raw.houseOnly, 'houseOnly', false);
100
+ const placements = parseIdList(raw.placements, 'placements', GAME_EXCHANGE_DECLARABLE_PLACEMENTS, GAME_EXCHANGE_DEFAULT_PLACEMENTS);
101
+ const formats = parseIdList(raw.formats, 'formats', GAME_EXCHANGE_FORMATS, GAME_EXCHANGE_DEFAULT_FORMATS);
102
+ const refuse = (problem) => ({ ok: false, error: `The gameExchange declaration's ${problem}` });
103
+ if (typeof listed === 'string')
104
+ return refuse(listed);
105
+ if (typeof houseOnly === 'string')
106
+ return refuse(houseOnly);
107
+ if (typeof placements === 'string')
108
+ return refuse(placements);
109
+ if (typeof formats === 'string')
110
+ return refuse(formats);
111
+ return { ok: true, value: { enabled, listed, houseOnly, placements, formats } };
112
+ }
113
+ export const EXCHANGE_CLICK_STEPS = ['visit', 'play', 'engaged'];
114
+ //# sourceMappingURL=game-exchange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-exchange.js","sourceRoot":"","sources":["../src/game-exchange.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,MAAM,CAAC,MAAM,wBAAwB,GAAqC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC9G,MAAM,CAAC,MAAM,mCAAmC,GAA+C,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAChH,MAAM,CAAC,MAAM,qBAAqB,GAAkC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,gCAAgC,GAA+C,CAAC,KAAK,CAAC,CAAC;AACpG,MAAM,CAAC,MAAM,6BAA6B,GAAkC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAEhG,6FAA6F;AAC7F,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,GAAG,CAAC;AAEvD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,8FAA8F;AAC9F,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAC9C,iCAAiC;AACjC,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC;AACrD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,8BAA8B,GAAG,oBAAoB,CAAC;AAEnE,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAQ,wBAA+C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,KAAc;IAC9D,OAAQ,mCAA0D,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAQ,qBAA4C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAmBD,MAAM,gBAAgB,GAAsB,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAExG,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,MAAM,CAAC,GAAsB;IACpC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAClB,KAAc,EACd,GAAW,EACX,OAAqB,EACrB,QAAsB;IAEtB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,uBAAuB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;IACnF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,oDAAoD,CAAC;IAC3F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAE,OAA8B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5F,OAAO,IAAI,GAAG,SAAS,KAAK,yBAAyB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;QAC1E,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,GAAW,EAAE,QAAiB;IAC/D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,GAAG,0BAA0B,CAAC;IACzE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,GAAY;IACvD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAAC;IACnG,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oDAAoD,UAAU,eAAe,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;IACxI,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,8DAA8D,EAAE,CAAC;IAC9F,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC5B,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,YAAY,EAAE,mCAAmC,EAAE,gCAAgC,CAAC,CAAC;IACpI,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,6BAA6B,CAAC,CAAC;IAC1G,MAAM,MAAM,GAAG,CAAC,OAAe,EAAiC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kCAAkC,OAAO,EAAE,EAAE,CAAC,CAAC;IACvI,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5D,IAAI,OAAO,UAAU,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9D,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;AAClF,CAAC;AAmGD,MAAM,CAAC,MAAM,oBAAoB,GAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitmagic/asset-core",
3
- "version": "0.2.33-dev.0",
3
+ "version": "0.2.33",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "author": "Bitmagic Oy",
6
6
  "type": "module",
@@ -39,6 +39,10 @@
39
39
  "types": "./dist/ad-package.d.ts",
40
40
  "default": "./dist/ad-package.js"
41
41
  },
42
+ "./game-exchange": {
43
+ "types": "./dist/game-exchange.d.ts",
44
+ "default": "./dist/game-exchange.js"
45
+ },
42
46
  "./reference-record": {
43
47
  "types": "./dist/reference-record.d.ts",
44
48
  "default": "./dist/reference-record.js"