@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 +11 -0
- package/lib/BasePortal.js +30 -0
- package/lib/CrazyGamesPortal.js +190 -0
- package/lib/Discord.js +144 -0
- package/lib/DiscordPortal.js +34 -0
- package/lib/FacebookPortal.js +81 -0
- package/lib/GameDistributionPortal.js +72 -0
- package/lib/Portal.js +44 -0
- package/lib/SteamPortal.js +52 -0
- package/lib/WebPortal.js +30 -0
- package/lib/index.js +5 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -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
|
+
}
|
package/lib/WebPortal.js
ADDED
|
@@ -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
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
|
+
}
|