@makefully/adaptfully 2.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/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +430 -0
- package/bin/wrapfully-deploy.js +7 -0
- package/lib/node/archive.js +34 -0
- package/lib/node/config.js +42 -0
- package/lib/node/deploy.js +69 -0
- package/lib/node/distribution.js +138 -0
- package/lib/node/index.js +18 -0
- package/lib/node/report.js +42 -0
- package/lib/runtime/auth/_helpers.js +38 -0
- package/lib/runtime/auth/dev-auth.js +78 -0
- package/lib/runtime/auth/google-auth.js +176 -0
- package/lib/runtime/auth/steam-auth.js +50 -0
- package/lib/runtime/core.js +61 -0
- package/lib/runtime/platform.js +89 -0
- package/package.json +61 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
7
|
+
const RUNTIME_DIR = path.join(PACKAGE_ROOT, 'lib', 'runtime');
|
|
8
|
+
|
|
9
|
+
/** @typedef {'web' | 'steam' | 'android' | 'ios'} BuildChannel */
|
|
10
|
+
|
|
11
|
+
export const VALID_CHANNELS = new Set(['web', 'steam', 'android', 'ios']);
|
|
12
|
+
|
|
13
|
+
/** @type {Record<BuildChannel, string[]>} */
|
|
14
|
+
const CHANNEL_EXCLUDED_AUTH = {
|
|
15
|
+
steam: ['auth/google-auth.js'],
|
|
16
|
+
web: ['auth/steam-auth.js'],
|
|
17
|
+
android: ['auth/steam-auth.js'],
|
|
18
|
+
ios: ['auth/steam-auth.js'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** @type {Record<BuildChannel, string>} */
|
|
22
|
+
const CHANNEL_EXT_SCRIPTS = {
|
|
23
|
+
web: '<script src="https://accounts.google.com/gsi/client" async defer></script>\n',
|
|
24
|
+
android: '<script src="https://accounts.google.com/gsi/client" async defer></script>\n',
|
|
25
|
+
ios: '<script src="https://accounts.google.com/gsi/client" async defer></script>\n',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** @type {Record<BuildChannel, string>} */
|
|
29
|
+
const CHANNEL_AUTH_REGISTRATION = {
|
|
30
|
+
web: "adaptfully.register('auth', adaptfully.auth.Google);",
|
|
31
|
+
android: "adaptfully.register('auth', adaptfully.auth.Google);",
|
|
32
|
+
ios: "adaptfully.register('auth', adaptfully.auth.Google);",
|
|
33
|
+
steam: "adaptfully.register('auth', adaptfully.auth.Steam);",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const DEV_AUTH_REGISTRATION = "adaptfully.register('auth', adaptfully.auth.Dev);";
|
|
37
|
+
|
|
38
|
+
const RUNTIME_BASE_SCRIPTS = [
|
|
39
|
+
'core.js',
|
|
40
|
+
'platform.js',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export function getPackageRoot() {
|
|
44
|
+
return PACKAGE_ROOT;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getRuntimeDir() {
|
|
48
|
+
return RUNTIME_DIR;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function resolveRuntimeScript(relativePath) {
|
|
52
|
+
return path.join(RUNTIME_DIR, relativePath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
57
|
+
* @returns {BuildChannel}
|
|
58
|
+
*/
|
|
59
|
+
export function getBuildChannel(env = process.env) {
|
|
60
|
+
const channel = env.ENTANGLEMENT_CHANNEL
|
|
61
|
+
|| env.VITE_ENTANGLEMENT_CHANNEL
|
|
62
|
+
|| env.ADAPTFULLY_CHANNEL
|
|
63
|
+
|| 'web';
|
|
64
|
+
|
|
65
|
+
if (!VALID_CHANNELS.has(channel)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Invalid distribution channel "${channel}". Expected one of: ${[...VALID_CHANNELS].join(', ')}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return /** @type {BuildChannel} */ (channel);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {BuildChannel} [channel] */
|
|
75
|
+
export function authRegistrationForChannel(channel = getBuildChannel()) {
|
|
76
|
+
const registration = CHANNEL_AUTH_REGISTRATION[channel];
|
|
77
|
+
if (!registration) {
|
|
78
|
+
throw new Error(`No auth registration for channel: ${channel}`);
|
|
79
|
+
}
|
|
80
|
+
return registration;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function devAuthRegistration() {
|
|
84
|
+
return DEV_AUTH_REGISTRATION;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @param {BuildChannel} [channel] */
|
|
88
|
+
export function getAuthScriptsForChannel(channel = getBuildChannel()) {
|
|
89
|
+
const excluded = new Set(CHANNEL_EXCLUDED_AUTH[channel] ?? []);
|
|
90
|
+
const scripts = RUNTIME_BASE_SCRIPTS.map((rel) => resolveRuntimeScript(rel));
|
|
91
|
+
|
|
92
|
+
const authDir = path.join(RUNTIME_DIR, 'auth');
|
|
93
|
+
for (const file of fs.readdirSync(authDir).sort()) {
|
|
94
|
+
const rel = path.join('auth', file).replace(/\\/g, '/');
|
|
95
|
+
if (!excluded.has(rel)) {
|
|
96
|
+
scripts.push(path.join(authDir, file));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return scripts;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @param {BuildChannel} [channel] */
|
|
104
|
+
export function authRegistrationScript(channel = getBuildChannel()) {
|
|
105
|
+
return authRegistrationForChannel(channel);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @param {BuildChannel} [channel] */
|
|
109
|
+
export function extScriptsForBuildChannel(channel = getBuildChannel()) {
|
|
110
|
+
return CHANNEL_EXT_SCRIPTS[channel] ?? '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {Array<string | { src: string }>} includes
|
|
115
|
+
* @param {BuildChannel} [channel]
|
|
116
|
+
*/
|
|
117
|
+
export function filterIncludesForBuildChannel(includes, channel = getBuildChannel()) {
|
|
118
|
+
if (channel !== 'steam') {
|
|
119
|
+
return includes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return includes.filter((include) => {
|
|
123
|
+
const rel = typeof include === 'string' ? include : include.src;
|
|
124
|
+
return rel !== 'script/banner-ads.js';
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {BuildChannel} channel
|
|
130
|
+
* @param {{ profiles: object, allExpansionsBitmask?: number }} distributionConfig
|
|
131
|
+
*/
|
|
132
|
+
export function distributionSettingsForBuild(channel, distributionConfig) {
|
|
133
|
+
return {
|
|
134
|
+
channel,
|
|
135
|
+
profiles: distributionConfig.profiles,
|
|
136
|
+
allExpansionsBitmask: distributionConfig.allExpansionsBitmask ?? 15,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { createArchive } from './archive.js';
|
|
2
|
+
export { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
3
|
+
export { deployFromCli, send } from './deploy.js';
|
|
4
|
+
export {
|
|
5
|
+
authRegistrationForChannel,
|
|
6
|
+
authRegistrationScript,
|
|
7
|
+
devAuthRegistration,
|
|
8
|
+
distributionSettingsForBuild,
|
|
9
|
+
extScriptsForBuildChannel,
|
|
10
|
+
filterIncludesForBuildChannel,
|
|
11
|
+
getAuthScriptsForChannel,
|
|
12
|
+
getBuildChannel,
|
|
13
|
+
getPackageRoot,
|
|
14
|
+
getRuntimeDir,
|
|
15
|
+
resolveRuntimeScript,
|
|
16
|
+
VALID_CHANNELS,
|
|
17
|
+
} from './distribution.js';
|
|
18
|
+
export { printBuildReport } from './report.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} builder
|
|
5
|
+
* @param {{ name: string, version: string }} pkg
|
|
6
|
+
*/
|
|
7
|
+
export function printBuildReport(builder, pkg) {
|
|
8
|
+
const statusPath = './output/wrapfully-status.json';
|
|
9
|
+
const legacyPath = `./output/${pkg.name}-${pkg.version}-${builder}.txt`;
|
|
10
|
+
|
|
11
|
+
let status = null;
|
|
12
|
+
if (fs.existsSync(statusPath)) {
|
|
13
|
+
try {
|
|
14
|
+
status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
15
|
+
} catch {
|
|
16
|
+
console.warn('Unable to read wrapfully-status.json');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (status) {
|
|
21
|
+
for (const event of status.events) {
|
|
22
|
+
const line = `[${event.step}] ${event.message}`;
|
|
23
|
+
if (event.level === 'error') {
|
|
24
|
+
console.error(line);
|
|
25
|
+
} else if (event.level === 'warn') {
|
|
26
|
+
console.warn(line);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!status.ok) {
|
|
33
|
+
console.error('Wrapfully build finished with errors.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(legacyPath)) {
|
|
40
|
+
console.log(fs.readFileSync(legacyPath, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{ id: string, email: string }} AuthUser
|
|
5
|
+
* @typedef {(result: { authenticated: boolean, user?: AuthUser | null }) => void} AuthCallback
|
|
6
|
+
* @typedef {(err?: { error?: string }) => void} ReadyCallback
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} AuthPlugin
|
|
9
|
+
* @property {string} name
|
|
10
|
+
* @property {(done: ReadyCallback) => void} whenReady
|
|
11
|
+
* @property {(callback: AuthCallback) => void} login
|
|
12
|
+
* @property {(callback: AuthCallback) => void} silentLogin
|
|
13
|
+
* @property {(callback: () => void) => void} logout
|
|
14
|
+
* @property {() => AuthUser | null} getUser
|
|
15
|
+
* @property {() => boolean} isAuthenticated
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
(function registerAuthHelpers(ns) {
|
|
19
|
+
const helpers = {
|
|
20
|
+
configValue(key, fallback) {
|
|
21
|
+
if (!ns.has('config')) {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
const config = ns.get('config');
|
|
25
|
+
if (config && config[key] != null) {
|
|
26
|
+
return config[key];
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
getStorage() {
|
|
32
|
+
return ns.has('storage') ? ns.get('storage') : null;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
ns.auth = ns.auth || {};
|
|
37
|
+
ns.auth.helpers = helpers;
|
|
38
|
+
}(window.adaptfully));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* global fetch, window */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local development auth — test values for dev builds outside the Wrapfully flow.
|
|
5
|
+
*
|
|
6
|
+
* adaptfully.register('auth', adaptfully.auth.Dev);
|
|
7
|
+
*/
|
|
8
|
+
(function registerDevAuth(ns) {
|
|
9
|
+
const { getStorage } = ns.auth.helpers;
|
|
10
|
+
|
|
11
|
+
const apiBase = () => {
|
|
12
|
+
if (ns.has('config')) {
|
|
13
|
+
const config = ns.get('config');
|
|
14
|
+
if (config && typeof config.apiBase === 'string') {
|
|
15
|
+
return config.apiBase;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (typeof window.__ADAPTFULLY_API__ === 'string') {
|
|
19
|
+
return window.__ADAPTFULLY_API__;
|
|
20
|
+
}
|
|
21
|
+
return '';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const apiGet = (path) => fetch(`${apiBase()}${path}`, { credentials: 'include' })
|
|
25
|
+
.then((response) => response.json());
|
|
26
|
+
|
|
27
|
+
class DevAuthPlugin {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.user = { id: 'dev-local-user', email: 'dev@local' };
|
|
30
|
+
this.authenticated = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
whenReady(done) {
|
|
34
|
+
done();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
login(callback) {
|
|
38
|
+
apiGet('/get-user')
|
|
39
|
+
.then((user) => {
|
|
40
|
+
if (user?.id) {
|
|
41
|
+
this.user = { id: user.id, email: user.email || 'dev@local' };
|
|
42
|
+
this.authenticated = true;
|
|
43
|
+
}
|
|
44
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
45
|
+
})
|
|
46
|
+
.catch(() => {
|
|
47
|
+
this.user = { id: 'dev-local-user', email: 'dev@local' };
|
|
48
|
+
this.authenticated = true;
|
|
49
|
+
callback({ authenticated: true, user: this.getUser() });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
silentLogin(callback) {
|
|
54
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logout(callback) {
|
|
58
|
+
const storage = getStorage();
|
|
59
|
+
this.authenticated = false;
|
|
60
|
+
this.user = null;
|
|
61
|
+
storage?.remove('lastLoggedIn');
|
|
62
|
+
callback();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getUser() {
|
|
66
|
+
if (!this.authenticated || !this.user) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return { id: this.user.id, email: this.user.email };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
isAuthenticated() {
|
|
73
|
+
return !!this.authenticated;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ns.auth.Dev = () => new DevAuthPlugin();
|
|
78
|
+
}(window.adaptfully));
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/* global google, sessionStorage, window */
|
|
2
|
+
|
|
3
|
+
(function registerGoogleAuth(ns) {
|
|
4
|
+
const { configValue, getStorage } = ns.auth.helpers;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CLIENT_ID = '225754014403.apps.googleusercontent.com';
|
|
7
|
+
const DEFAULT_SCOPES = 'openid email profile';
|
|
8
|
+
const DEFAULT_TOKEN_KEY = 'adaptfully_google_token';
|
|
9
|
+
|
|
10
|
+
class GoogleAuthPlugin {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.name = 'google';
|
|
13
|
+
this.tokenClient = null;
|
|
14
|
+
this.tokenKey = configValue('googleTokenKey', DEFAULT_TOKEN_KEY);
|
|
15
|
+
this.clientId = configValue('googleClientId', DEFAULT_CLIENT_ID);
|
|
16
|
+
this.scopes = configValue('googleScopes', DEFAULT_SCOPES);
|
|
17
|
+
this.accessToken = sessionStorage.getItem(this.tokenKey) || '';
|
|
18
|
+
this.user = null;
|
|
19
|
+
this.authenticated = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
whenReady(done) {
|
|
23
|
+
const setup = () => {
|
|
24
|
+
this.tokenClient = google.accounts.oauth2.initTokenClient({
|
|
25
|
+
client_id: this.clientId,
|
|
26
|
+
scope: this.scopes,
|
|
27
|
+
callback: () => {},
|
|
28
|
+
});
|
|
29
|
+
done();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (window.google?.accounts?.oauth2) {
|
|
33
|
+
setup();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let attempts = 0;
|
|
38
|
+
const timer = window.setInterval(() => {
|
|
39
|
+
attempts += 1;
|
|
40
|
+
if (window.google?.accounts?.oauth2) {
|
|
41
|
+
window.clearInterval(timer);
|
|
42
|
+
setup();
|
|
43
|
+
} else if (attempts > 200) {
|
|
44
|
+
window.clearInterval(timer);
|
|
45
|
+
console.error('Google Identity Services failed to load.');
|
|
46
|
+
done({ error: 'Google Identity Services failed to load.' });
|
|
47
|
+
}
|
|
48
|
+
}, 50);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#applyUserInfo(data) {
|
|
52
|
+
this.user = {
|
|
53
|
+
id: data.sub || data.id || '',
|
|
54
|
+
email: data.email || '',
|
|
55
|
+
};
|
|
56
|
+
this.authenticated = !!(this.user.id && this.user.email);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#clearSession() {
|
|
60
|
+
this.accessToken = '';
|
|
61
|
+
this.user = null;
|
|
62
|
+
this.authenticated = false;
|
|
63
|
+
sessionStorage.removeItem(this.tokenKey);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#hasPersistedLogin() {
|
|
67
|
+
const storage = getStorage();
|
|
68
|
+
return !!(storage?.get('lastLoggedIn'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#fetchUserInfo(token, callback) {
|
|
72
|
+
fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
73
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
74
|
+
})
|
|
75
|
+
.then((response) => {
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error('userinfo failed');
|
|
78
|
+
}
|
|
79
|
+
return response.json();
|
|
80
|
+
})
|
|
81
|
+
.then((data) => {
|
|
82
|
+
this.accessToken = token;
|
|
83
|
+
sessionStorage.setItem(this.tokenKey, token);
|
|
84
|
+
this.#applyUserInfo(data);
|
|
85
|
+
callback();
|
|
86
|
+
})
|
|
87
|
+
.catch(() => {
|
|
88
|
+
this.#clearSession();
|
|
89
|
+
callback();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#requestAccessToken(prompt, callback) {
|
|
94
|
+
this.tokenClient.callback = (response) => {
|
|
95
|
+
if (response.error || !response.access_token) {
|
|
96
|
+
this.#clearSession();
|
|
97
|
+
callback();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.#fetchUserInfo(response.access_token, callback);
|
|
101
|
+
};
|
|
102
|
+
this.tokenClient.requestAccessToken({ prompt: prompt || '' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#restoreSession(callback) {
|
|
106
|
+
const trySilentGoogleLogin = () => {
|
|
107
|
+
if (this.#hasPersistedLogin()) {
|
|
108
|
+
this.#requestAccessToken('none', callback);
|
|
109
|
+
} else {
|
|
110
|
+
this.#clearSession();
|
|
111
|
+
callback();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (this.accessToken) {
|
|
116
|
+
this.#fetchUserInfo(this.accessToken, () => {
|
|
117
|
+
if (this.authenticated) {
|
|
118
|
+
callback();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
trySilentGoogleLogin();
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
trySilentGoogleLogin();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
login(callback) {
|
|
129
|
+
if (this.authenticated) {
|
|
130
|
+
callback({ authenticated: true, user: this.getUser() });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.#requestAccessToken('select_account', () => {
|
|
134
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
silentLogin(callback) {
|
|
139
|
+
if (this.authenticated && this.user?.id) {
|
|
140
|
+
callback({ authenticated: true, user: this.getUser() });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.#restoreSession(() => {
|
|
144
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
logout(callback) {
|
|
149
|
+
const storage = getStorage();
|
|
150
|
+
const finish = () => {
|
|
151
|
+
this.#clearSession();
|
|
152
|
+
storage?.remove('lastLoggedIn');
|
|
153
|
+
callback();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (this.accessToken && google.accounts?.oauth2) {
|
|
157
|
+
google.accounts.oauth2.revoke(this.accessToken, finish);
|
|
158
|
+
} else {
|
|
159
|
+
finish();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getUser() {
|
|
164
|
+
if (!this.authenticated || !this.user) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return { id: this.user.id, email: this.user.email };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
isAuthenticated() {
|
|
171
|
+
return !!this.authenticated;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
ns.auth.Google = () => new GoogleAuthPlugin();
|
|
176
|
+
}(window.adaptfully));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Steam auth plugin — placeholder for Electron / NW.js Steam deployments.
|
|
5
|
+
* Wire Steamworks login here; games only talk to adaptfully.get('auth').
|
|
6
|
+
*/
|
|
7
|
+
(function registerSteamAuth(ns) {
|
|
8
|
+
const { getStorage } = ns.auth.helpers;
|
|
9
|
+
|
|
10
|
+
class SteamAuthPlugin {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.name = 'steam';
|
|
13
|
+
this.user = null;
|
|
14
|
+
this.authenticated = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
whenReady(done) {
|
|
18
|
+
done();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
login(callback) {
|
|
22
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
silentLogin(callback) {
|
|
26
|
+
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
logout(callback) {
|
|
30
|
+
const storage = getStorage();
|
|
31
|
+
this.user = null;
|
|
32
|
+
this.authenticated = false;
|
|
33
|
+
storage?.remove('lastLoggedIn');
|
|
34
|
+
callback();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getUser() {
|
|
38
|
+
if (!this.authenticated || !this.user) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return { id: this.user.id, email: this.user.email || '' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isAuthenticated() {
|
|
45
|
+
return !!this.authenticated;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ns.auth.Steam = () => new SteamAuthPlugin();
|
|
50
|
+
}(window.adaptfully));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adaptfully — shared platform services for Makefully games.
|
|
5
|
+
*
|
|
6
|
+
* adaptfully.register('auth', adaptfully.auth.Google);
|
|
7
|
+
* const platform = adaptfully.get('auth');
|
|
8
|
+
*/
|
|
9
|
+
(function initAdaptfully(global) {
|
|
10
|
+
class Adaptfully {
|
|
11
|
+
/** @type {Record<string, unknown>} */
|
|
12
|
+
#registry = {};
|
|
13
|
+
|
|
14
|
+
/** @type {import('./platform.js').Platform | null} */
|
|
15
|
+
#authPlatform = null;
|
|
16
|
+
|
|
17
|
+
register(key, value) {
|
|
18
|
+
if (key === 'auth') {
|
|
19
|
+
this.#authPlatform = null;
|
|
20
|
+
}
|
|
21
|
+
this.#registry[key] = value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(key) {
|
|
25
|
+
if (key === 'auth') {
|
|
26
|
+
if (!this.#authPlatform) {
|
|
27
|
+
const factory = this.#registry.auth;
|
|
28
|
+
if (!factory) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'adaptfully: no auth registered — call adaptfully.register(\'auth\', ...) before the game loads',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const plugin = typeof factory === 'function' ? factory() : factory;
|
|
34
|
+
if (!this.Platform) {
|
|
35
|
+
throw new Error('adaptfully: Platform is not loaded');
|
|
36
|
+
}
|
|
37
|
+
this.#authPlatform = new this.Platform(plugin);
|
|
38
|
+
}
|
|
39
|
+
return this.#authPlatform;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!(key in this.#registry)) {
|
|
43
|
+
throw new Error(`adaptfully: nothing registered for "${key}"`);
|
|
44
|
+
}
|
|
45
|
+
return this.#registry[key];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
has(key) {
|
|
49
|
+
return key in this.#registry;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @deprecated Use adaptfully.get('auth') */
|
|
53
|
+
getInstance() {
|
|
54
|
+
return this.get('auth');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const adaptfully = global.adaptfully || new Adaptfully();
|
|
59
|
+
adaptfully.auth = adaptfully.auth || {};
|
|
60
|
+
global.adaptfully = adaptfully;
|
|
61
|
+
}(typeof window !== 'undefined' ? window : globalThis));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
(function registerPlatform(ns) {
|
|
4
|
+
class Platform {
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} authPlugin
|
|
7
|
+
*/
|
|
8
|
+
constructor(authPlugin) {
|
|
9
|
+
this.auth = authPlugin;
|
|
10
|
+
this.online = true;
|
|
11
|
+
this.#ready = false;
|
|
12
|
+
this.#queue = [];
|
|
13
|
+
|
|
14
|
+
authPlugin.whenReady((err) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
this.online = false;
|
|
17
|
+
}
|
|
18
|
+
this.#ready = true;
|
|
19
|
+
const queue = this.#queue;
|
|
20
|
+
this.#queue = [];
|
|
21
|
+
for (const callback of queue) {
|
|
22
|
+
callback();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
#ready;
|
|
29
|
+
|
|
30
|
+
/** @type {Array<() => void>} */
|
|
31
|
+
#queue;
|
|
32
|
+
|
|
33
|
+
whenReady(callback) {
|
|
34
|
+
if (this.#ready) {
|
|
35
|
+
callback();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.#queue.push(callback);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
login(callback) {
|
|
42
|
+
this.whenReady(() => {
|
|
43
|
+
if (!this.online) {
|
|
44
|
+
callback({ authenticated: false });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.auth.login((result) => {
|
|
48
|
+
callback(result || {
|
|
49
|
+
authenticated: this.auth.isAuthenticated(),
|
|
50
|
+
user: this.auth.getUser(),
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
silentLogin(callback) {
|
|
57
|
+
this.whenReady(() => {
|
|
58
|
+
if (!this.online) {
|
|
59
|
+
callback({ authenticated: false });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.auth.silentLogin((result) => {
|
|
63
|
+
callback(result || {
|
|
64
|
+
authenticated: this.auth.isAuthenticated(),
|
|
65
|
+
user: this.auth.getUser(),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logout(callback) {
|
|
72
|
+
this.whenReady(() => {
|
|
73
|
+
this.auth.logout(() => {
|
|
74
|
+
callback({ authenticated: false });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getUser() {
|
|
80
|
+
return this.auth.getUser();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isAuthenticated() {
|
|
84
|
+
return this.auth.isAuthenticated();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ns.Platform = Platform;
|
|
89
|
+
}(window.adaptfully));
|