@cardsjd/portal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Portal
2
+
3
+ Use:
4
+
5
+ ```js
6
+ import { Portal } from '@cardsjd/portal';
7
+
8
+ const portal = Portal.create(platform, appConfig, userdata, advertisementConfig);
9
+ ```
10
+
11
+ The package exports the portal factory and concrete portal classes.
@@ -0,0 +1,30 @@
1
+ export class BasePortal {
2
+
3
+ async init() { }
4
+
5
+ async load() { }
6
+
7
+ async save(data, forceSave) { }
8
+
9
+ async delete() { }
10
+
11
+ gameStart() { }
12
+
13
+ gameStop() { }
14
+
15
+ loadStart() { }
16
+
17
+ loadStop() { }
18
+
19
+ async getInvite() { }
20
+
21
+ async inviteSend(user, room) { }
22
+
23
+ async findAndInvite(userFrom, room) { }
24
+
25
+ async getFriends() { }
26
+
27
+ tryShowInterstitialAd() { }
28
+
29
+ createShortCut() { }
30
+ }
@@ -0,0 +1,190 @@
1
+ import { BasePortal } from './BasePortal';
2
+
3
+ export class CrazyGamesPortal extends BasePortal {
4
+ name = 'CrazyGamesPortal';
5
+ autoLogin = true;
6
+ hideMoreGames = true;
7
+ hideSuggestions = false;
8
+ hideReportBug = false;
9
+
10
+ async init() {
11
+ try {
12
+ await import('https://sdk.crazygames.com/crazygames-sdk-v3.js');
13
+
14
+ const sdk = globalThis.CrazyGames.SDK;
15
+ await sdk.init();
16
+ } catch (err) {
17
+ console.error('failed to load crazygames', err);
18
+ }
19
+
20
+ }
21
+ isCrazyGames() {
22
+ if (typeof globalThis.CrazyGames === 'undefined') return false;
23
+ if (!globalThis.CrazyGames.SDK) return false;
24
+ if (globalThis.CrazyGames.SDK.environment === "disabled") {
25
+ console.log('CrazyGames SDK is disabled');
26
+ return false;
27
+ }
28
+ return true;
29
+ }
30
+ async load() {
31
+ if (!this.isCrazyGames()) return;
32
+
33
+ const data = {};
34
+ try {
35
+ data.userdata = await this.loadData();
36
+ data.user = await this.loadUser();
37
+ } catch (err) {
38
+ console.warn('Failed to call CrazyGames SDK load:', err);
39
+ }
40
+ return data;
41
+ }
42
+ async save(data, forceSave) {
43
+ if (!this.isCrazyGames()) {
44
+ return;
45
+ }
46
+
47
+ const sdk = globalThis.CrazyGames.SDK;
48
+ if (!sdk.data) return;
49
+
50
+ sdk.data.setItem("userdata", JSON.stringify(data));
51
+
52
+ }
53
+ async delete() {
54
+ if (!this.isCrazyGames()) {
55
+ return;
56
+ }
57
+ const sdk = globalThis.CrazyGames.SDK;
58
+ if (!sdk.data) return;
59
+
60
+ sdk.data.clear();
61
+ }
62
+ async loadData() {
63
+ if (!this.isCrazyGames()) {
64
+ return null;
65
+ }
66
+ const sdk = globalThis.CrazyGames.SDK;
67
+ if (!sdk.data) return null;
68
+
69
+ //get userdata from crazygames storage - and get newest data
70
+ const userdata_crazyGame_string = sdk.data.getItem('userdata');
71
+ if (!userdata_crazyGame_string) {
72
+ return null;
73
+ }
74
+
75
+ try {
76
+ const portalUserdata = JSON.parse(userdata_crazyGame_string);
77
+ return portalUserdata;
78
+ } catch (err) {
79
+ console.warn('Failed to parse CrazyGames userdata:', err);
80
+ return null;
81
+ }
82
+
83
+ }
84
+ async loadUser() {
85
+ if (!this.isCrazyGames()) {
86
+ return null;
87
+ }
88
+
89
+ const sdk = globalThis.CrazyGames.SDK;
90
+ if (!sdk.data) return null;
91
+
92
+
93
+
94
+ if (!sdk.user.isUserAccountAvailable) {
95
+ return null;
96
+ }
97
+
98
+ const user = {};
99
+ try {
100
+ const userdetails = await sdk.user.getUser();
101
+
102
+ // Add null check for userdetails
103
+ if (!userdetails) {
104
+ console.warn('CrazyGames getUser returned null/undefined');
105
+ return null;
106
+ }
107
+
108
+ if (userdetails?.username) {
109
+ user.name = userdetails.username;
110
+ user.crazyGamesId = userdetails.username; //use username for now, not actual userid from crazy games, too many steps and higher risk of failure
111
+ }
112
+ if (userdetails?.profilePictureUrl) {
113
+ user.avatar = userdetails.profilePictureUrl;
114
+ }
115
+ } catch (err) {
116
+ console.warn('Failed to get CrazyGames user details:', err);
117
+ return null;
118
+ }
119
+
120
+ ////options #1 - Get or Create User ID
121
+ ////cascade login from most complicated to least complicated safe failing method
122
+ ////////// getting Crazy Game userid is disabled for now, it will slow the server down too much - Rely on auto created accounts for now and the local stoage and crazy games data restore.
123
+ // // // //get userToken
124
+ // // // try {
125
+ // // // const userToken = await window.CrazyGames.SDK.user.getUserToken();
126
+ // // // if (userToken) {
127
+ // // // const data = await this.accountJD.CrazyGameUserData(userToken);
128
+ // // // if (data) {
129
+ // // // if (data.id) userdata.crazyGamesId = data.id;
130
+ // // // }
131
+ // // // }
132
+ // // // } catch (e) { }
133
+
134
+
135
+ return user;
136
+ }
137
+
138
+ gameStart() {
139
+ if (!this.isCrazyGames()) {
140
+ return;
141
+ }
142
+
143
+ try {
144
+ const sdk = globalThis.CrazyGames.SDK;
145
+ sdk.game.gameplayStart();
146
+ } catch (err) {
147
+ console.warn('Failed to call gameplayStart:', err);
148
+ }
149
+ }
150
+
151
+ gameStop() {
152
+ if (!this.isCrazyGames()) {
153
+ return;
154
+ }
155
+
156
+ try {
157
+ const sdk = globalThis.CrazyGames.SDK;
158
+ sdk.game.gameplayStop();
159
+ } catch (err) {
160
+ console.warn('Failed to call gameplayStop:', err);
161
+ }
162
+ }
163
+
164
+ loadStart() {
165
+ if (!this.isCrazyGames()) {
166
+ return;
167
+ }
168
+
169
+ try {
170
+ const sdk = globalThis.CrazyGames.SDK;
171
+ sdk.game.loadingStart();
172
+ } catch (err) {
173
+ console.warn('Failed to call loadingStart:', err);
174
+ }
175
+ }
176
+
177
+ loadStop() {
178
+ if (!this.isCrazyGames()) {
179
+ return;
180
+ }
181
+
182
+ try {
183
+ const sdk = globalThis.CrazyGames.SDK;
184
+ sdk.game.loadingStop();
185
+ } catch (err) {
186
+ console.warn('Failed to call loadingStop:', err);
187
+ }
188
+ }
189
+
190
+ }
package/lib/Discord.js ADDED
@@ -0,0 +1,144 @@
1
+ import { DiscordSDK } from "@discord/embedded-app-sdk";
2
+
3
+ export default class Discord {
4
+ constructor(options) {
5
+ this.client_id = options.discordClientId;
6
+ this.discordSdk = null;
7
+ this.auth = null;
8
+ }
9
+ isOnDiscord() {
10
+ //check url for discord
11
+ const isOnDiscord = window.location.host.includes("discord") || (window.location.href.includes("localhost") &&window.location.href.includes("frame_id"));
12
+ return isOnDiscord;
13
+ }
14
+ async getData() {
15
+ //get name and avatar
16
+ let auth = await this.setupDiscordSdk();
17
+ let guildMemeber = await this.getGuildMember(auth.access_token);
18
+ let name = this.getUserDisplayName(auth.user, guildMemeber);
19
+ console.log("Discord Name", name);
20
+ let avatar = this.getUserAvatarUrl(auth.user, guildMemeber);
21
+ console.log("Discord Avatar", avatar);
22
+ const discordId = guildMemeber?.user?.id;
23
+ let data = {
24
+ name: name,
25
+ avatar: avatar,
26
+ guildId: this.discordSdk.guildId,
27
+ guildMemeber: guildMemeber,
28
+ discordId: discordId
29
+ }
30
+ return data;
31
+ }
32
+ async setupDiscordSdk() {
33
+ const clientId = this.client_id || import.meta.env.VITE_DISCORD_CLIENT_ID;
34
+ console.log("Initializing Discord SDK with Client ID:", clientId);
35
+ this.discordSdk = new DiscordSDK(clientId);
36
+ await this.discordSdk.ready();
37
+ console.log("Discord SDK is ready");
38
+
39
+ // Authorize with Discord Client
40
+ const { code } = await this.discordSdk.commands.authorize({
41
+ client_id: clientId,
42
+ response_type: "code",
43
+ state: "",
44
+ prompt: "none",
45
+ scope: [
46
+ "identify",
47
+ "applications.commands",
48
+ "guilds",
49
+ "guilds.members.read"
50
+ ],
51
+ });
52
+
53
+ // Retrieve an access_token from your activity's server
54
+ //const response = await fetch("/.proxy/api/token", {
55
+ const response = await fetch("/api/token/discord", {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({
61
+ code: code,
62
+ ClientId: clientId
63
+ }),
64
+ });
65
+ const { access_token } = await response.json();
66
+
67
+ // Authenticate with Discord client (using the access_token)
68
+ this.auth = await this.discordSdk.commands.authenticate({
69
+ access_token,
70
+ });
71
+ console.log("Authenticated with Discord Client", this.auth);
72
+ if (this.auth == null) {
73
+ throw new Error("Authenticate command failed");
74
+ }
75
+ return this.auth;
76
+ }
77
+ async getGuildMember(access_token, discordSdk) {
78
+ if (!access_token) auth = this.auth.access_token;
79
+ if (!discordSdk) discordSdk = this.discordSdk;
80
+ // Get guild specific nickname and avatar, and fallback to user name and avatar
81
+ const guildMember = await fetch(
82
+ `https://discord.com/api/v10/users/@me/guilds/${discordSdk.guildId}/member`,
83
+ {
84
+ method: 'get',
85
+ headers: { Authorization: `Bearer ${access_token}` },
86
+ },
87
+ )
88
+ .then((j) => j.json())
89
+ .catch(() => {
90
+ return null;
91
+ });
92
+ return guildMember;
93
+ }
94
+ getUserDisplayName(user, guildMember) {
95
+ if (guildMember?.nick != null && guildMember.nick !== '') return guildMember.nick;
96
+
97
+ if (user.discriminator !== '0') return `${user.username}#${user.discriminator}`;
98
+
99
+ if (user.global_name != null && user.global_name !== '') return user.global_name;
100
+
101
+ return user.username;
102
+ }
103
+
104
+ getUserAvatarUrl(user, guildMember, discordSdk, cdn = `https://cdn.discordapp.com`, size = 256) {
105
+ if (!user) user = this.auth.user;
106
+ if (!discordSdk) discordSdk = this.discordSdk;
107
+
108
+ if (guildMember?.avatar != null && discordSdk.guildId != null) {
109
+ return `${cdn}/guilds/${discordSdk.guildId}/users/${user.id}/avatars/${guildMember.avatar}.png?size=${size}`;
110
+ }
111
+ if (user.avatar != null) {
112
+ return `${cdn}/avatars/${user.id}/${user.avatar}.png?size=${size}`;
113
+ }
114
+
115
+ const defaultAvatarIndex = Math.abs(Number(user.id) >> 22) % 6;
116
+ return `${cdn}/embed/avatars/${defaultAvatarIndex}.png?size=${size}`;
117
+ }
118
+
119
+ async appendGuildAvatar() {
120
+ const app = document.querySelector('#app');
121
+
122
+ // 1. From the HTTP API fetch a list of all of the user's guilds
123
+ const guilds = await fetch(`https://discord.com/api/v10/users/@me/guilds`, {
124
+ headers: {
125
+ // NOTE: we're using the access_token provided by the "authenticate" command
126
+ Authorization: `Bearer ${this.auth.access_token}`,
127
+ 'Content-Type': 'application/json',
128
+ },
129
+ }).then((response) => response.json());
130
+
131
+ // 2. Find the current guild's info, including it's "icon"
132
+ const currentGuild = guilds.find((g) => g.id === this.discordSdk.guildId);
133
+
134
+ // 3. Append to the UI an img tag with the related information
135
+ if (currentGuild != null) {
136
+ return `https://cdn.discordapp.com/icons/${currentGuild.id}/${currentGuild.icon}.webp?size=128`;
137
+ }
138
+ else {
139
+ return null;
140
+ }
141
+ }
142
+
143
+
144
+ }
@@ -0,0 +1,34 @@
1
+ import { BasePortal } from './BasePortal';
2
+ import Discord from './Discord.js';
3
+
4
+ export class DiscordPortal extends BasePortal {
5
+ name = 'DiscordPortal';
6
+ discord = null;
7
+
8
+ constructor(config) {
9
+ super();
10
+ this.discord = new Discord(config || {});
11
+ }
12
+
13
+ async load() {
14
+ if (!this.discord?.isOnDiscord?.()) {
15
+ return {};
16
+ }
17
+
18
+ const data = await this.discord.getData();
19
+ if (!data) {
20
+ return {};
21
+ }
22
+
23
+ return {
24
+ user: {
25
+ name: data.name,
26
+ avatar: data.avatar,
27
+ avatar_discord: data.avatar,
28
+ discordId: data.discordId,
29
+ guildId: data.guildId,
30
+ roomId: data.guildId
31
+ }
32
+ };
33
+ }
34
+ }
@@ -0,0 +1,81 @@
1
+ import { BasePortal } from './BasePortal';
2
+ import { FBInstantGame } from '@cardsjd/fbinstantgame';
3
+
4
+ export class FacebookPortal extends BasePortal {
5
+ name = 'FacebookPortal';
6
+ fBInstantGame = null;
7
+
8
+ constructor(config, advertisementConfig) {
9
+ super();
10
+ this.fBInstantGame = new FBInstantGame(config);
11
+ this.advertisementConfig = advertisementConfig;
12
+ }
13
+ async init() {
14
+ await this.fBInstantGame.initialize();
15
+ }
16
+ isAvailable() {
17
+
18
+ return true;
19
+ }
20
+ async load() {
21
+ const fbUserdata = await this.fBInstantGame.loadData();
22
+ const fbProfile = await this.fBInstantGame.getProfile();
23
+ const data = {};
24
+ data.userdata = fbUserdata;
25
+ data.user = fbProfile;
26
+ return data;
27
+ }
28
+ async save(data, forceSave) {
29
+ this.fBInstantGame.saveData(data, forceSave).catch(function () { });
30
+ }
31
+ gameStart() {
32
+ }
33
+
34
+ gameStop() {
35
+ }
36
+
37
+ loadStart() {
38
+ //it is inside the data load fBInstantGame.initialize or fBInstantGame.loadData
39
+ }
40
+
41
+ loadStop() {
42
+ //it is inside the data load fBInstantGame.initialize or fBInstantGame.loadData
43
+
44
+ //try create shortcut after load (could be a portal level function, for now just call it here until other platforms need it)
45
+ this.createShortCutTry();
46
+ this.subscribeBot();
47
+ }
48
+ createShortCut() {
49
+ this.portal?.createShortCut?.();
50
+ }
51
+ async subscribeBot() {
52
+ await this.fBInstantGame.subscribeBot();
53
+ }
54
+ createShortCut() {
55
+ this.fBInstantGame.createShortcut();
56
+ }
57
+ async getInvite() {
58
+ //returns {message,room}
59
+ return this.fBInstantGame.getInvite();
60
+ }
61
+ async invite(userId, room) {
62
+ return this.fBInstantGame.invitePlayer(userId, room);
63
+ }
64
+ async findAndInvite(userFrom, room) {
65
+ return this.fBInstantGame.inviteSend(userFrom, room);
66
+ }
67
+ async getFriends() {
68
+ const fbUsers = await this.fBInstantGame.getConnectedPlayersAsync();
69
+ if (!fbUsers) return [];
70
+ const friends = [];
71
+ for (let i = 0; i < fbUsers.length; i++) {
72
+ const fbUser = fbUsers[i];
73
+
74
+ //Facebook only gaves the facebookid, no name or userid
75
+ const facebookId = fbUser.getID();
76
+ friends.push({ facebookId: facebookId });
77
+ }
78
+ return friends;
79
+ }
80
+
81
+ }
@@ -0,0 +1,72 @@
1
+ import { BasePortal } from './BasePortal';
2
+
3
+ const isGameDistribution = () => typeof globalThis.gdsdk !== 'undefined';
4
+
5
+ export class GameDistributionPortal extends BasePortal {
6
+ name = 'GameDistributionPortal';
7
+ isInitialized = false;
8
+ isError = false;
9
+
10
+ async init() {
11
+
12
+ }
13
+
14
+ async load(userdata, advertisementConfig) {
15
+ if (!this.isInitialized && advertisementConfig.gameDistributionId) {
16
+ await this.#doInit(advertisementConfig.gameDistributionId);
17
+ }
18
+
19
+ if (!isGameDistribution()) {
20
+ return;
21
+ }
22
+ }
23
+
24
+ tryShowInterstitialAd() {
25
+ if (!isGameDistribution()) {
26
+ return;
27
+ }
28
+
29
+ globalThis?.gdsdk?.showAd?.();
30
+ }
31
+
32
+ async #doInit(gameId) {
33
+ if (!this.isError) {
34
+ globalThis.GD_OPTIONS = {
35
+ gameId,
36
+ onEvent: (event) => {
37
+ this.#handleEvent(event);
38
+ },
39
+ };
40
+
41
+ try {
42
+ await import('https://html5.api.gamedistribution.com/main.min.js');
43
+ this.isInitialized = true;
44
+ } catch (err) {
45
+ console.error('Failed to load GameDistribution');
46
+ this.isError = true;
47
+ }
48
+ }
49
+ }
50
+
51
+ async #handleEvent(event) {
52
+ console.log('got event from GD', event);
53
+
54
+ switch (event.name) {
55
+ case 'SDK_GAME_START':
56
+ // advertisement done, resume game logic and unmute audio
57
+ break;
58
+ case 'SDK_GAME_PAUSE':
59
+ // pause game logic / mute audio
60
+ break;
61
+ case 'SDK_GDPR_TRACKING':
62
+ // this event is triggered when your user doesn't want to be tracked
63
+ break;
64
+ case 'SDK_GDPR_TARGETING':
65
+ // this event is triggered when your user doesn't want personalised targeting of ads and such
66
+ break;
67
+ case 'SDK_REWARDED_WATCH_COMPLETE':
68
+ // this event is triggered when your user completely watched rewarded ad
69
+ break;
70
+ }
71
+ }
72
+ }
package/lib/Portal.js ADDED
@@ -0,0 +1,44 @@
1
+ import { Device } from '@cardsjd/device';
2
+ import { BasePortal } from './BasePortal.js';
3
+ import { CrazyGamesPortal } from './CrazyGamesPortal.js';
4
+ import { FacebookPortal } from './FacebookPortal.js';
5
+ import { WebPortal } from './WebPortal.js';
6
+ import { SteamPortal } from './SteamPortal.js';
7
+ import { DiscordPortal } from './DiscordPortal.js';
8
+ //import { GameDistributionPortal } from './GameDistributionPortal';
9
+
10
+ const PortalLoaders = {
11
+ crazygames: CrazyGamesPortal,
12
+ facebook: FacebookPortal,
13
+ web: WebPortal,
14
+ windowsPWA: WebPortal,
15
+ windows: WebPortal,
16
+ stream: SteamPortal,
17
+ discord: DiscordPortal,
18
+ Discord: DiscordPortal
19
+ //gamedistribution: GameDistributionPortal,
20
+ };
21
+
22
+
23
+ //Factory class for creating platform-specific portal instances.
24
+ export class Portal {
25
+ /**
26
+ * Creates and initializes a portal instance based on the current platform.
27
+ * @param {string} platform - The platform identifier
28
+ * @param {Object} appConfig - Configuration object to pass to the portal
29
+ * @param {Object} advertisementConfig - Configuration object to pass to the portal
30
+ * @returns {BasePortal} The initialized portal instance
31
+ */
32
+ static create(platform, appConfig, advertisementConfig) {
33
+
34
+ if (!platform) {
35
+ const device = new Device();
36
+ platform = device.getPlatform();
37
+ }
38
+
39
+ const portalClass = PortalLoaders[platform] || BasePortal;
40
+ const portal = new portalClass(appConfig, advertisementConfig);
41
+
42
+ return portal;
43
+ }
44
+ }
@@ -0,0 +1,52 @@
1
+ import { BasePortal } from './BasePortal';
2
+
3
+ export class SteamPortal extends BasePortal {
4
+ name = 'SteamPortal';
5
+ config = {};
6
+ constructor(config) {
7
+ super();
8
+ this.config.version = config?.version;
9
+ }
10
+ async init() {
11
+ }
12
+ async load() {
13
+ const params = new URLSearchParams(window.location.search);
14
+ const steamUserdata = {};
15
+ const steamProfile = {};
16
+
17
+ const name = params.get('name');
18
+ const avatar = params.get('avatar');
19
+ const steamId = params.get('steamId');
20
+
21
+ if (name) {
22
+ steamUserdata.name = name;
23
+ steamProfile.name = name;
24
+ }
25
+ if (steamId) {
26
+ steamUserdata.steamId = steamId;
27
+ }
28
+
29
+ let resolvedAvatar = avatar;
30
+ if (!resolvedAvatar && steamId) {
31
+ resolvedAvatar = await this.getSteamAvatar(steamId);
32
+ }
33
+ if (resolvedAvatar) {
34
+ steamUserdata.avatar = resolvedAvatar;
35
+ steamProfile.avatar = resolvedAvatar;
36
+ }
37
+
38
+ return {
39
+ userdata: steamUserdata,
40
+ user: steamProfile
41
+ };
42
+ }
43
+ async getSteamAvatar(steamId) {
44
+ let url = `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=B362E8813B70415ADF5002D05C4353C5&steamids=[${steamId}]`;
45
+ let response = await fetch(url);
46
+ let data = await response.json();
47
+
48
+ if (!data || !data.response || !data.response.players || !data.response.players[0]) return;
49
+ return data.response.players[0].avatarfull;
50
+ }
51
+
52
+ }
@@ -0,0 +1,30 @@
1
+ import { BasePortal } from './BasePortal';
2
+
3
+ export class WebPortal extends BasePortal {
4
+ name = 'WebPortal';
5
+ autoLogin = false;
6
+ config = {};
7
+ constructor(config) {
8
+ super();
9
+ this.config.version = config?.version;
10
+ }
11
+ async init() {
12
+ //set update google analytics dynamically
13
+ //some platforms may block google analytics, so only add it for web portal
14
+ this.addGoogleAnalytics(this.config.version); //no await no - fire and forget
15
+ }
16
+ //todo: move to lib so other portals can use it too
17
+ async addGoogleAnalytics(version) {
18
+ console.log('WebPortal: Initializing Google Analytics');
19
+ //set update google analytics dynamically
20
+ await import('https://www.googletagmanager.com/gtag/js?id=G-BMWB7H1PDJ');
21
+ window.dataLayer = window.dataLayer || [];
22
+ function gtag() { dataLayer.push(arguments); }
23
+ gtag('js', new Date());
24
+
25
+ gtag('config', 'G-BMWB7H1PDJ', {
26
+ app_version: version
27
+ });// for Cribbage all platforms
28
+ gtag('config', 'G-SBZ83TJLW6');// for website cardsjd.com
29
+ }
30
+ }
package/lib/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { Portal } from './Portal';
2
+
3
+ export {
4
+ Portal
5
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@cardsjd/portal",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "lib"
12
+ ],
13
+ "scripts": {
14
+ "lint": "eslint lib --report-unused-disable-directives --max-warnings 0",
15
+ "lint:fix": "npm run lint -- --fix"
16
+ },
17
+ "dependencies": {
18
+ "@cardsjd/device": "^0.1.14",
19
+ "@cardsjd/fbinstantgame": "^0.1.12",
20
+ "@discord/embedded-app-sdk": "^2.4.0"
21
+ },
22
+ "peerDependencies": {
23
+ "@sagi.io/globalthis": "^0.0.2"
24
+ }
25
+ }