@makefully/adaptfully 3.0.2 → 3.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 +28 -0
- package/README.md +22 -4
- package/lib/node/registrations.js +17 -1
- package/lib/runtime/auth/_helpers.js +2 -1
- package/lib/runtime/auth/dev-auth.js +5 -1
- package/lib/runtime/auth/google-auth.js +38 -11
- package/lib/runtime/auth/steam-auth.js +5 -1
- package/lib/runtime/platform.js +12 -2
- package/lib/runtime/storage/_helpers.js +5 -0
- package/lib/runtime/storage/indexed-db.js +87 -0
- package/lib/runtime/storage/local-storage.js +50 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.1.0 — 2026-06-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Built-in storage plugins: `localStorage` (sync) and `indexedDB` (async).
|
|
10
|
+
- Register with `"storage": "localStorage"` or `"storage": "indexedDB"` in `config.platforms.<platform>.registrations`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Auth helpers use registered `storage` only (no implicit fallbacks).
|
|
15
|
+
|
|
16
|
+
## 3.0.3 — 2026-06-18
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `platform.autoLogin()` and `platform.supportsAutoLogin()` for session restore without UI.
|
|
21
|
+
- Google auth reads `config.autoLoginStorageKey` (default: `lastLoggedIn`) when deciding whether to attempt silent OAuth.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Auth plugins expose `autoLogin()` only (removed `silentLogin()`).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Google auth reads OAuth config and stored tokens lazily so bridge-registered `config`/`storage` are applied before auto-login runs.
|
|
30
|
+
- Prebuild injects deploy bridge scripts after `core.js` and before auth plugin runtime scripts.
|
|
31
|
+
- Auth storage helper falls back to `localStorage` when `storage` is not registered via Adaptfully.
|
|
32
|
+
|
|
5
33
|
## 3.0.2 — 2026-06-18
|
|
6
34
|
|
|
7
35
|
### Fixed
|
package/README.md
CHANGED
|
@@ -45,6 +45,21 @@ const platform = adaptfully.get('auth');
|
|
|
45
45
|
platform.login(function (result) { /* ... */ });
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Storage plugins
|
|
49
|
+
|
|
50
|
+
| Plugin key | Registration | Runtime |
|
|
51
|
+
|------------|--------------|---------|
|
|
52
|
+
| `localStorage` | `adaptfully.register('storage', adaptfully.storage.LocalStorage())` | Sync key/value storage in the browser |
|
|
53
|
+
| `indexedDB` | `adaptfully.register('storage', adaptfully.storage.IndexedDB())` | Async key/value storage for larger payloads |
|
|
54
|
+
|
|
55
|
+
In-game:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
const storage = adaptfully.get('storage');
|
|
59
|
+
storage.set('playerName', 'Ada');
|
|
60
|
+
storage.getObject('currentGame');
|
|
61
|
+
```
|
|
62
|
+
|
|
48
63
|
### Auth plugins
|
|
49
64
|
|
|
50
65
|
| Plugin key | Registration | Runtime |
|
|
@@ -61,18 +76,19 @@ Use plugin keys in `config.platforms.<platform>.registrations`. Custom deploy sc
|
|
|
61
76
|
"platforms": {
|
|
62
77
|
"steam": {
|
|
63
78
|
"registrations": {
|
|
64
|
-
"
|
|
65
|
-
"
|
|
79
|
+
"storage": "localStorage",
|
|
80
|
+
"auth": "steam-auth"
|
|
66
81
|
}
|
|
67
82
|
},
|
|
68
83
|
"web": {
|
|
69
84
|
"registrations": {
|
|
70
|
-
"
|
|
71
|
-
"
|
|
85
|
+
"storage": "localStorage",
|
|
86
|
+
"auth": "google-auth"
|
|
72
87
|
}
|
|
73
88
|
},
|
|
74
89
|
"dev": {
|
|
75
90
|
"registrations": {
|
|
91
|
+
"storage": "localStorage",
|
|
76
92
|
"auth": "dev-auth"
|
|
77
93
|
}
|
|
78
94
|
}
|
|
@@ -107,6 +123,8 @@ import {
|
|
|
107
123
|
- **`prebuildPlatform(deployFolder, platformKey, pkg)`** — copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` (default: `index.html`).
|
|
108
124
|
- **`resolveRegistrationAssets(registrations)`** — resolve runtime script paths, inline registration JS, and external script tags for a registration map (useful for Vite dev servers).
|
|
109
125
|
- **`runAdaptfullyStage('prebuild' | 'build' | 'deploy', platformKey, options)`** — run a pipeline stage programmatically.
|
|
126
|
+
- **`platform.autoLogin(callback)`** — restore a prior session without UI when the auth plugin supports it (Google uses `lastLoggedIn` in storage and a cached OAuth token).
|
|
127
|
+
- **`platform.supportsAutoLogin()`** — whether the active auth plugin can attempt automatic sign-in.
|
|
110
128
|
|
|
111
129
|
---
|
|
112
130
|
|
|
@@ -25,6 +25,14 @@ export const STANDARD_PLUGINS = {
|
|
|
25
25
|
scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/dev-auth.js'],
|
|
26
26
|
registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Dev);`,
|
|
27
27
|
},
|
|
28
|
+
localStorage: {
|
|
29
|
+
scripts: ['core.js', 'storage/_helpers.js', 'storage/local-storage.js'],
|
|
30
|
+
registration: (key) => `adaptfully.register('${key}', adaptfully.storage.LocalStorage());`,
|
|
31
|
+
},
|
|
32
|
+
indexedDB: {
|
|
33
|
+
scripts: ['core.js', 'storage/_helpers.js', 'storage/indexed-db.js'],
|
|
34
|
+
registration: (key) => `adaptfully.register('${key}', adaptfully.storage.IndexedDB());`,
|
|
35
|
+
},
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
/** Default Wrapfully builder → config.platforms key */
|
|
@@ -201,7 +209,11 @@ export function buildAdaptfullyInjection(registrations, options = {}) {
|
|
|
201
209
|
block += `${extScript}\n`;
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
|
|
212
|
+
const coreScript = 'core.js';
|
|
213
|
+
const bootstrapScripts = parts.runtimeScripts.filter((script) => script === coreScript);
|
|
214
|
+
const pluginScripts = parts.runtimeScripts.filter((script) => script !== coreScript);
|
|
215
|
+
|
|
216
|
+
for (const script of bootstrapScripts) {
|
|
205
217
|
block += `<script>\n${readRuntimeScript(script)}\n</script>\n`;
|
|
206
218
|
}
|
|
207
219
|
|
|
@@ -209,6 +221,10 @@ export function buildAdaptfullyInjection(registrations, options = {}) {
|
|
|
209
221
|
block += `<script src="${src}"></script>\n`;
|
|
210
222
|
}
|
|
211
223
|
|
|
224
|
+
for (const script of pluginScripts) {
|
|
225
|
+
block += `<script>\n${readRuntimeScript(script)}\n</script>\n`;
|
|
226
|
+
}
|
|
227
|
+
|
|
212
228
|
if (parts.inlineRegistrations.length > 0) {
|
|
213
229
|
block += `<script>\n${parts.inlineRegistrations.join('\n')}\n</script>\n`;
|
|
214
230
|
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* @property {string} name
|
|
10
10
|
* @property {(done: ReadyCallback) => void} whenReady
|
|
11
11
|
* @property {(callback: AuthCallback) => void} login
|
|
12
|
-
* @property {(callback: AuthCallback) => void}
|
|
12
|
+
* @property {(callback: AuthCallback) => void} autoLogin
|
|
13
|
+
* @property {() => boolean} [supportsAutoLogin]
|
|
13
14
|
* @property {(callback: () => void) => void} logout
|
|
14
15
|
* @property {() => AuthUser | null} getUser
|
|
15
16
|
* @property {() => boolean} isAuthenticated
|
|
@@ -50,10 +50,14 @@
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
autoLogin(callback) {
|
|
54
54
|
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
supportsAutoLogin() {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
logout(callback) {
|
|
58
62
|
const storage = getStorage();
|
|
59
63
|
this.authenticated = false;
|
|
@@ -6,24 +6,42 @@
|
|
|
6
6
|
const DEFAULT_CLIENT_ID = '225754014403.apps.googleusercontent.com';
|
|
7
7
|
const DEFAULT_SCOPES = 'openid email profile';
|
|
8
8
|
const DEFAULT_TOKEN_KEY = 'adaptfully_google_token';
|
|
9
|
+
const DEFAULT_AUTO_LOGIN_KEY = 'lastLoggedIn';
|
|
9
10
|
|
|
10
11
|
class GoogleAuthPlugin {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.name = 'google';
|
|
13
14
|
this.tokenClient = null;
|
|
14
|
-
this.
|
|
15
|
-
this.clientId = configValue('googleClientId', DEFAULT_CLIENT_ID);
|
|
16
|
-
this.scopes = configValue('googleScopes', DEFAULT_SCOPES);
|
|
17
|
-
this.accessToken = sessionStorage.getItem(this.tokenKey) || '';
|
|
15
|
+
this.accessToken = '';
|
|
18
16
|
this.user = null;
|
|
19
17
|
this.authenticated = false;
|
|
20
18
|
}
|
|
21
19
|
|
|
20
|
+
supportsAutoLogin() {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#tokenKey() {
|
|
25
|
+
return configValue('googleTokenKey', DEFAULT_TOKEN_KEY);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#clientId() {
|
|
29
|
+
return configValue('googleClientId', DEFAULT_CLIENT_ID);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#scopes() {
|
|
33
|
+
return configValue('googleScopes', DEFAULT_SCOPES);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#autoLoginStorageKey() {
|
|
37
|
+
return configValue('autoLoginStorageKey', DEFAULT_AUTO_LOGIN_KEY);
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
whenReady(done) {
|
|
23
41
|
const setup = () => {
|
|
24
42
|
this.tokenClient = google.accounts.oauth2.initTokenClient({
|
|
25
|
-
client_id: this
|
|
26
|
-
scope: this
|
|
43
|
+
client_id: this.#clientId(),
|
|
44
|
+
scope: this.#scopes(),
|
|
27
45
|
callback: () => {},
|
|
28
46
|
});
|
|
29
47
|
done();
|
|
@@ -60,12 +78,19 @@
|
|
|
60
78
|
this.accessToken = '';
|
|
61
79
|
this.user = null;
|
|
62
80
|
this.authenticated = false;
|
|
63
|
-
sessionStorage.removeItem(this
|
|
81
|
+
sessionStorage.removeItem(this.#tokenKey());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#loadStoredToken() {
|
|
85
|
+
if (!this.accessToken) {
|
|
86
|
+
this.accessToken = sessionStorage.getItem(this.#tokenKey()) || '';
|
|
87
|
+
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
#hasPersistedLogin() {
|
|
67
91
|
const storage = getStorage();
|
|
68
|
-
|
|
92
|
+
const key = this.#autoLoginStorageKey();
|
|
93
|
+
return !!(storage?.get(key));
|
|
69
94
|
}
|
|
70
95
|
|
|
71
96
|
#fetchUserInfo(token, callback) {
|
|
@@ -80,7 +105,7 @@
|
|
|
80
105
|
})
|
|
81
106
|
.then((data) => {
|
|
82
107
|
this.accessToken = token;
|
|
83
|
-
sessionStorage.setItem(this
|
|
108
|
+
sessionStorage.setItem(this.#tokenKey(), token);
|
|
84
109
|
this.#applyUserInfo(data);
|
|
85
110
|
callback();
|
|
86
111
|
})
|
|
@@ -103,6 +128,8 @@
|
|
|
103
128
|
}
|
|
104
129
|
|
|
105
130
|
#restoreSession(callback) {
|
|
131
|
+
this.#loadStoredToken();
|
|
132
|
+
|
|
106
133
|
const trySilentGoogleLogin = () => {
|
|
107
134
|
if (this.#hasPersistedLogin()) {
|
|
108
135
|
this.#requestAccessToken('none', callback);
|
|
@@ -135,7 +162,7 @@
|
|
|
135
162
|
});
|
|
136
163
|
}
|
|
137
164
|
|
|
138
|
-
|
|
165
|
+
autoLogin(callback) {
|
|
139
166
|
if (this.authenticated && this.user?.id) {
|
|
140
167
|
callback({ authenticated: true, user: this.getUser() });
|
|
141
168
|
return;
|
|
@@ -149,7 +176,7 @@
|
|
|
149
176
|
const storage = getStorage();
|
|
150
177
|
const finish = () => {
|
|
151
178
|
this.#clearSession();
|
|
152
|
-
storage?.remove(
|
|
179
|
+
storage?.remove(this.#autoLoginStorageKey());
|
|
153
180
|
callback();
|
|
154
181
|
};
|
|
155
182
|
|
|
@@ -22,10 +22,14 @@
|
|
|
22
22
|
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
autoLogin(callback) {
|
|
26
26
|
callback({ authenticated: this.authenticated, user: this.getUser() });
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
supportsAutoLogin() {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
logout(callback) {
|
|
30
34
|
const storage = getStorage();
|
|
31
35
|
this.user = null;
|
package/lib/runtime/platform.js
CHANGED
|
@@ -53,13 +53,17 @@
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
autoLogin(callback) {
|
|
57
57
|
this.whenReady(() => {
|
|
58
58
|
if (!this.online) {
|
|
59
59
|
callback({ authenticated: false });
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
this.auth.
|
|
62
|
+
if (typeof this.auth.autoLogin !== 'function') {
|
|
63
|
+
callback({ authenticated: false });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.auth.autoLogin((result) => {
|
|
63
67
|
callback(result || {
|
|
64
68
|
authenticated: this.auth.isAuthenticated(),
|
|
65
69
|
user: this.auth.getUser(),
|
|
@@ -68,6 +72,12 @@
|
|
|
68
72
|
});
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
supportsAutoLogin() {
|
|
76
|
+
return typeof this.auth.supportsAutoLogin === 'function'
|
|
77
|
+
? this.auth.supportsAutoLogin()
|
|
78
|
+
: typeof this.auth.autoLogin === 'function';
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
logout(callback) {
|
|
72
82
|
this.whenReady(() => {
|
|
73
83
|
this.auth.logout(() => {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* global indexedDB, window */
|
|
2
|
+
|
|
3
|
+
(function registerIndexedDBStorage(ns) {
|
|
4
|
+
function configValue(key, fallback) {
|
|
5
|
+
if (!ns.has('config')) {
|
|
6
|
+
return fallback;
|
|
7
|
+
}
|
|
8
|
+
const config = ns.get('config');
|
|
9
|
+
return config?.[key] != null ? config[key] : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ns.storage.IndexedDB = function indexedDBFactory() {
|
|
13
|
+
const dbName = configValue('indexedDBName', 'adaptfully');
|
|
14
|
+
const storeName = configValue('indexedDBStoreName', 'storage');
|
|
15
|
+
/** @type {Promise<IDBDatabase> | null} */
|
|
16
|
+
let dbPromise = null;
|
|
17
|
+
|
|
18
|
+
function openDb() {
|
|
19
|
+
if (!dbPromise) {
|
|
20
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
21
|
+
const request = indexedDB.open(dbName, 1);
|
|
22
|
+
request.onupgradeneeded = () => {
|
|
23
|
+
request.result.createObjectStore(storeName);
|
|
24
|
+
};
|
|
25
|
+
request.onsuccess = () => resolve(request.result);
|
|
26
|
+
request.onerror = () => reject(request.error);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return dbPromise;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function withStore(mode, fn) {
|
|
33
|
+
return openDb().then((db) => new Promise((resolve, reject) => {
|
|
34
|
+
const tx = db.transaction(storeName, mode);
|
|
35
|
+
const store = tx.objectStore(storeName);
|
|
36
|
+
const request = fn(store);
|
|
37
|
+
request.onsuccess = () => resolve(request.result);
|
|
38
|
+
request.onerror = () => reject(request.error);
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
name: 'indexedDB',
|
|
44
|
+
async get(field) {
|
|
45
|
+
try {
|
|
46
|
+
const value = await withStore('readonly', (store) => store.get(field));
|
|
47
|
+
return value === undefined ? false : value;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async set(field, value) {
|
|
53
|
+
await this.remove(field);
|
|
54
|
+
try {
|
|
55
|
+
await withStore('readwrite', (store) => store.put(value, field));
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
async remove(field) {
|
|
61
|
+
try {
|
|
62
|
+
await withStore('readwrite', (store) => store.delete(field));
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
async getObject(field) {
|
|
68
|
+
const raw = await this.get(field);
|
|
69
|
+
if (!raw) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(raw);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async setObject(field, obj) {
|
|
79
|
+
try {
|
|
80
|
+
await this.set(field, JSON.stringify(obj));
|
|
81
|
+
} catch {
|
|
82
|
+
// ignore
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}(window.adaptfully));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* global localStorage, window */
|
|
2
|
+
|
|
3
|
+
(function registerLocalStorage(ns) {
|
|
4
|
+
ns.storage.LocalStorage = function localStorageFactory() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'localStorage',
|
|
7
|
+
get(field) {
|
|
8
|
+
try {
|
|
9
|
+
const value = localStorage.getItem(field);
|
|
10
|
+
return value === null ? false : value;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
set(field, value) {
|
|
16
|
+
this.remove(field);
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem(field, value);
|
|
19
|
+
} catch {
|
|
20
|
+
// quota or private browsing
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
remove(field) {
|
|
24
|
+
try {
|
|
25
|
+
localStorage.removeItem(field);
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
getObject(field) {
|
|
31
|
+
const raw = this.get(field);
|
|
32
|
+
if (!raw) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(raw);
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
setObject(field, obj) {
|
|
42
|
+
try {
|
|
43
|
+
this.set(field, JSON.stringify(obj));
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}(window.adaptfully));
|