@makefully/adaptfully 3.9.0 → 3.11.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 +24 -0
- package/README.md +46 -17
- package/lib/assets/icon-background.png +0 -0
- package/lib/assets/icon-foreground.png +0 -0
- package/lib/node/archive.js +18 -2
- package/lib/node/config.js +1 -1
- package/lib/node/icons.js +97 -0
- package/lib/node/index.js +11 -1
- package/lib/node/packagers/base.js +8 -0
- package/lib/node/packagers/capacitor.js +126 -0
- package/lib/node/packagers/index.js +12 -5
- package/lib/node/pipeline.js +26 -6
- package/lib/node/registrations.js +27 -9
- package/lib/runtime/auth/social-auth.js +313 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.11.0 — 2026-07-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Layered icon validation** — build/deploy/release zips always include `meta/icon-foreground.png` and `meta/icon-background.png`. When project icons are missing under `assets/meta/`, Adaptfully **warns** (does not fail) and ships packaged placeholder icons from `lib/assets/`.
|
|
10
|
+
- **`resolveMetaIcons` / `appendMetaIcons`** — exported helpers for resolving project vs placeholder icon paths.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Legacy meta packaging skips re-adding icon filenames so placeholders and project icons are not duplicated in the zip.
|
|
15
|
+
|
|
16
|
+
## 3.10.0 — 2026-07-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`social-auth`** — Capgo `@capgo/capacitor-social-login` auth plugin for Capacitor platforms. Requires `packager: "capacitor"` and `platforms.<name>.socialLogin` provider config. Prebuild writes `social-login-config.js`; Wrapfully installs the native plugin when `socialLogin` is set on the build spec.
|
|
21
|
+
- **`CapacitorPackager`** CSP/viewport HTML extras and social-auth validation/templates.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Builder family defaults** — `android` / `ios` / `ios-sim` (and `-dev`) resolve to Wrapfully family **`capacitor`**. Explicit `packager: "cordova"` still selects Cordova for that platform. Composite `cordova` / `apple` routes are not used.
|
|
26
|
+
- **`resolveBuildSpec`** includes `socialLogin: boolean`.
|
|
27
|
+
- **Single-target Wrapfully POSTs** go to the target route (`/android/build`, `/ios/release`, …) instead of the family route (`/capacitor/...`), so OS relay and existing target routes keep working.
|
|
28
|
+
|
|
5
29
|
## 3.9.0 — 2026-07-15
|
|
6
30
|
|
|
7
31
|
### Added
|
package/README.md
CHANGED
|
@@ -91,7 +91,8 @@ storage.getObject('currentGame');
|
|
|
91
91
|
|------------|--------------|---------|
|
|
92
92
|
| `google-auth` | `adaptfully.register('auth', adaptfully.auth.Google)` | Web, Android, iOS |
|
|
93
93
|
| `steam-auth` | `adaptfully.register('auth', adaptfully.auth.Steam)` | Steam / Electron (via [steamworks.js](https://github.com/ceifa/steamworks.js)) |
|
|
94
|
-
| `
|
|
94
|
+
| `social-auth` | `adaptfully.register('auth', adaptfully.auth.Social)` | Capacitor (via [@capgo/capacitor-social-login](https://github.com/Cap-go/capacitor-social-login)) |
|
|
95
|
+
| `dev-auth` | `adaptfully.register('auth', adaptfully.auth.Dev)` | Local / testing |
|
|
95
96
|
|
|
96
97
|
Use plugin keys in `config.platforms.<platform>.registrations`. Custom deploy scripts use a path relative to the deploy folder instead:
|
|
97
98
|
|
|
@@ -133,10 +134,14 @@ Each platform entry may set a **`packager`** (`web`, `electron`, `cordova`, or `
|
|
|
133
134
|
| `web` | `game-config.js` for **`uwp`** platform prebuilds |
|
|
134
135
|
| `electron` | `main.js` (Electron shell); `preload.js` when **`steam-auth`** is registered |
|
|
135
136
|
| `cordova` | `cordova.js` stub, `game-config.js`, CSP/viewport HTML extras |
|
|
136
|
-
| `capacitor` | `game-config.js`
|
|
137
|
+
| `capacitor` | `game-config.js`, Capacitor CSP/viewport; `social-login-config.js` when **`social-auth`** is registered |
|
|
138
|
+
|
|
139
|
+
Android/iOS Wrapfully builds default to the **`capacitor`** builder family. Set `packager: "cordova"` on a platform to use Cordova for that target.
|
|
137
140
|
|
|
138
141
|
**`steam-auth` requires `packager: "electron"`** and `platforms.<name>.steamId` (Steamworks app ID for the client preload). Steam **upload** app IDs live separately in each Steam deployment’s `manifest.json` — see Wrapfully docs.
|
|
139
142
|
|
|
143
|
+
**`social-auth` requires `packager: "capacitor"`** and `platforms.<name>.socialLogin` (Capgo provider client IDs). Wrapfully installs `@capgo/capacitor-social-login` when building.
|
|
144
|
+
|
|
140
145
|
```json
|
|
141
146
|
{
|
|
142
147
|
"config": {
|
|
@@ -149,6 +154,20 @@ Each platform entry may set a **`packager`** (`web`, `electron`, `cordova`, or `
|
|
|
149
154
|
"packager": "electron",
|
|
150
155
|
"steamId": 1234567,
|
|
151
156
|
"registrations": { "auth": "steam-auth" }
|
|
157
|
+
},
|
|
158
|
+
"android": {
|
|
159
|
+
"packager": "capacitor",
|
|
160
|
+
"registrations": { "auth": "social-auth", "storage": "localStorage" },
|
|
161
|
+
"socialLogin": {
|
|
162
|
+
"providers": { "google": true, "apple": true },
|
|
163
|
+
"google": {
|
|
164
|
+
"webClientId": "….apps.googleusercontent.com",
|
|
165
|
+
"iOSClientId": "….apps.googleusercontent.com"
|
|
166
|
+
},
|
|
167
|
+
"apple": {
|
|
168
|
+
"clientId": "com.example.service"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
152
171
|
}
|
|
153
172
|
}
|
|
154
173
|
}
|
|
@@ -169,6 +188,17 @@ The renderer `steam-auth` plugin reads the Steam ID from that bridge. When Steam
|
|
|
169
188
|
| `autoLoginStorageKey` | `lastLoggedIn` | Storage key written with the Steam ID on login |
|
|
170
189
|
| `steamReadyTimeoutMs` | `10000` | Max wait when a bridge exists but identity is not yet ready |
|
|
171
190
|
|
|
191
|
+
#### Social auth (`social-auth`)
|
|
192
|
+
|
|
193
|
+
When `social-auth` is registered on a **`capacitor`** platform, Adaptfully prebuild writes **`social-login-config.js`** (`window.__ADAPTFULLY_SOCIAL_LOGIN__`) from `platforms.<name>.socialLogin`. Wrapfully installs [`@capgo/capacitor-social-login`](https://github.com/Cap-go/capacitor-social-login) and syncs it into the native project (same role as `steamworks.js` for Electron).
|
|
194
|
+
|
|
195
|
+
The runtime plugin calls Capgo `SocialLogin.initialize` / `login` / `logout` / `isLoggedIn`. Default provider is `google` on Android and `apple` on iOS when both are enabled (override with `socialLogin.defaultProvider`). Optional config keys:
|
|
196
|
+
|
|
197
|
+
| Key | Default | Purpose |
|
|
198
|
+
|-----|---------|---------|
|
|
199
|
+
| `autoLoginStorageKey` | `lastLoggedIn` | Storage key written with the user id on login |
|
|
200
|
+
| `socialReadyTimeoutMs` | `10000` | Max wait for the Capgo plugin bridge |
|
|
201
|
+
|
|
172
202
|
### Node API
|
|
173
203
|
|
|
174
204
|
```javascript
|
|
@@ -211,7 +241,7 @@ After prebuild, the build and deploy stages zip `output/<platform>-prebuild/` an
|
|
|
211
241
|
|
|
212
242
|
```bash
|
|
213
243
|
npx adaptfully prebuild web
|
|
214
|
-
npx adaptfully deploy steam http://build.example.com:
|
|
244
|
+
npx adaptfully deploy steam http://build.example.com:9633/
|
|
215
245
|
```
|
|
216
246
|
|
|
217
247
|
For web-only hosting (no Wrapfully), stop after prebuild and upload `output/web-prebuild/` yourself.
|
|
@@ -246,10 +276,10 @@ Examples:
|
|
|
246
276
|
npx adaptfully prebuild web
|
|
247
277
|
|
|
248
278
|
# Build for Steam via Wrapfully
|
|
249
|
-
npx adaptfully build steam http://build.example.com:
|
|
279
|
+
npx adaptfully build steam http://build.example.com:9633/
|
|
250
280
|
|
|
251
281
|
# Full Steam deploy (build + upload when steam.json credentials are present)
|
|
252
|
-
npx adaptfully deploy steam http://build.example.com:
|
|
282
|
+
npx adaptfully deploy steam http://build.example.com:9633/
|
|
253
283
|
```
|
|
254
284
|
|
|
255
285
|
Add scripts to your project's `package.json`:
|
|
@@ -278,7 +308,7 @@ The server URL is resolved in this order:
|
|
|
278
308
|
1. CLI argument
|
|
279
309
|
2. `WRAPFULLY_SERVER` environment variable
|
|
280
310
|
3. `server` field in `wrapfully.json`
|
|
281
|
-
4. `http://localhost:
|
|
311
|
+
4. `http://localhost:9633/`
|
|
282
312
|
|
|
283
313
|
Keep server addresses and credentials out of version control — use environment variables or a gitignored `wrapfully.json`.
|
|
284
314
|
|
|
@@ -293,7 +323,7 @@ The client POSTs a zip stream built from `output/<platform>-prebuild/` to:
|
|
|
293
323
|
For example, a project named `mygame` at version `1.2.0` with builder `android`:
|
|
294
324
|
|
|
295
325
|
```
|
|
296
|
-
http://build.example.com:
|
|
326
|
+
http://build.example.com:9633/android/mygame-1.2.0
|
|
297
327
|
```
|
|
298
328
|
|
|
299
329
|
The server extracts the zip, reads the embedded `package.json`, runs the build for that platform, and streams a zip of artifacts back to the client.
|
|
@@ -333,7 +363,7 @@ mygame/
|
|
|
333
363
|
|
|
334
364
|
The selected deployment's folder is shipped as `meta/publish/` in the zip, so per-platform credential file names (`build.json`, `sftp.json`, `steam.json`, `apple.json`, `google.json`, `ms.json`, `android/`, `ms/`) live inside `assets/meta/deployments/<key>/`.
|
|
335
365
|
|
|
336
|
-
Icons (`icon-foreground.png`, `icon-background.png`) are required for mobile, desktop, and Steam builds.
|
|
366
|
+
Icons (`icon-foreground.png`, `icon-background.png`) are required for mobile, desktop, and Steam builds. If either file is missing from `assets/meta/`, Adaptfully **warns** and ships packaged placeholder icons for that build so Wrapfully can still run.
|
|
337
367
|
|
|
338
368
|
### Icons
|
|
339
369
|
|
|
@@ -348,6 +378,8 @@ The build server composites the foreground over the background, applies a bindin
|
|
|
348
378
|
|
|
349
379
|
**Recommended format:** 1536×1536 pixel square PNGs for both files. Images with other dimensions are scaled to 1536×1536 automatically, but matching the target size produces the sharpest results.
|
|
350
380
|
|
|
381
|
+
Missing icons do not fail the Adaptfully client: check the `WARNING missing layered icon(s)` log line and replace the placeholders before a store release.
|
|
382
|
+
|
|
351
383
|
## Configuration
|
|
352
384
|
|
|
353
385
|
Build settings are read from `package.json`. The client merges any `wrapfully.json` fields into `package.json`'s `config` object before sending.
|
|
@@ -415,6 +447,7 @@ Standard npm fields (`name`, `version`, `description`) are used directly. Add a
|
|
|
415
447
|
| `platforms.<name>.builders` | wrapfully-deploy | Map additional Wrapfully builder names to a platform |
|
|
416
448
|
| `platforms.<name>.packager` | Prebuild | `web` (default), `electron`, `cordova`, or `capacitor` — selects the packager class that adds platform-specific files during prebuild |
|
|
417
449
|
| `platforms.<name>.steamId` | Electron + steam-auth | Steamworks app ID baked into `preload.js` (client). Upload app IDs belong in the Steam deployment `manifest.json` |
|
|
450
|
+
| `platforms.<name>.socialLogin` | Capacitor + social-auth | Capgo provider config (`providers`, `google.webClientId`, `apple.clientId`, optional `defaultProvider`) |
|
|
418
451
|
| `properties` | Cordova | Cordova config.xml entries (plugins, allow-navigation, etc.) |
|
|
419
452
|
|
|
420
453
|
### `wrapfully.json`
|
|
@@ -424,7 +457,7 @@ Optional. Fields are shallow-merged into `package.json`'s `config`:
|
|
|
424
457
|
```json
|
|
425
458
|
{
|
|
426
459
|
"deployFolder": "dist",
|
|
427
|
-
"server": "http://build.example.com:
|
|
460
|
+
"server": "http://build.example.com:9633/",
|
|
428
461
|
"title": "My Game",
|
|
429
462
|
"packageName": "com.example.mygame"
|
|
430
463
|
}
|
|
@@ -453,12 +486,8 @@ Each builder name becomes a path segment on the server. Some builds require a sp
|
|
|
453
486
|
| `webapp` | Service-worker web app (optionally SFTP deploy) |
|
|
454
487
|
| `steam` | Windows + Mac + Linux, uploads to Steam |
|
|
455
488
|
| `steam-dev` | Debug Windows + Mac + Linux, no Steam upload |
|
|
456
|
-
| `cordova` | Release Android + iOS |
|
|
457
|
-
| `cordova-dev` | Debug Android + iOS |
|
|
458
|
-
| `apple` | Release Mac + iOS |
|
|
459
|
-
| `apple-dev` | Release Mac + debug iOS |
|
|
460
489
|
|
|
461
|
-
For a single platform, pass the specific builder name
|
|
490
|
+
For a single platform, pass the specific builder name.
|
|
462
491
|
|
|
463
492
|
### Platform package requirements
|
|
464
493
|
|
|
@@ -508,7 +537,7 @@ To deploy to Google Play, also include `assets/meta/deployments/<deployment>/goo
|
|
|
508
537
|
}
|
|
509
538
|
```
|
|
510
539
|
|
|
511
|
-
#### Apple (`ios`, `ios-dev`, `ios-sim`, `mac
|
|
540
|
+
#### Apple (`ios`, `ios-dev`, `ios-sim`, `mac`)
|
|
512
541
|
|
|
513
542
|
Include `assets/meta/deployments/<deployment>/build.json` with iOS signing settings:
|
|
514
543
|
|
|
@@ -544,9 +573,9 @@ To deploy to the App Store, include `assets/meta/deployments/<deployment>/apple.
|
|
|
544
573
|
}
|
|
545
574
|
```
|
|
546
575
|
|
|
547
|
-
#### Cordova
|
|
576
|
+
#### Cordova
|
|
548
577
|
|
|
549
|
-
Requires the Android and Apple package requirements above.
|
|
578
|
+
Set `packager: "cordova"` on an `android` or `ios` platform to use Cordova instead of Capacitor. Requires the Android and Apple package requirements above.
|
|
550
579
|
|
|
551
580
|
#### Steam (`steam`, `steam-dev`)
|
|
552
581
|
|
|
Binary file
|
|
Binary file
|
package/lib/node/archive.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import archiver from 'archiver';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { appendMetaIcons, isMetaIconFilename, META_DIR } from './icons.js';
|
|
4
5
|
|
|
5
6
|
const
|
|
6
|
-
META_DIR = 'assets/meta',
|
|
7
7
|
DEPLOYMENTS_DIRNAME = 'deployments',
|
|
8
8
|
PUBLISH_DIRNAME = 'publish';
|
|
9
9
|
|
|
@@ -38,6 +38,9 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
|
|
|
38
38
|
log = options.log ?? (() => {}),
|
|
39
39
|
metaDir = path.resolve(META_DIR);
|
|
40
40
|
|
|
41
|
+
// Always ship layered icons (project files or Adaptfully placeholders).
|
|
42
|
+
appendMetaIcons(zip, { metaDir, log });
|
|
43
|
+
|
|
41
44
|
if (!fs.existsSync(metaDir)) {
|
|
42
45
|
log(`adaptfully: meta dir missing at ${metaDir}; skipping credentials`);
|
|
43
46
|
return;
|
|
@@ -46,7 +49,17 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
|
|
|
46
49
|
const deploymentsRoot = path.join(metaDir, DEPLOYMENTS_DIRNAME);
|
|
47
50
|
|
|
48
51
|
if (!fs.existsSync(deploymentsRoot)) {
|
|
49
|
-
|
|
52
|
+
for (const entry of fs.readdirSync(metaDir, { withFileTypes: true })) {
|
|
53
|
+
if (isMetaIconFilename(entry.name)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const fullPath = path.join(metaDir, entry.name);
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
appendDirectory(zip, fullPath, `meta/${entry.name}`);
|
|
59
|
+
} else {
|
|
60
|
+
zip.file(fullPath, { name: `meta/${entry.name}` });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
50
63
|
return;
|
|
51
64
|
}
|
|
52
65
|
|
|
@@ -54,6 +67,9 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
|
|
|
54
67
|
if (entry.name === DEPLOYMENTS_DIRNAME || entry.name === PUBLISH_DIRNAME) {
|
|
55
68
|
continue;
|
|
56
69
|
}
|
|
70
|
+
if (isMetaIconFilename(entry.name)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
57
73
|
|
|
58
74
|
const fullPath = path.join(metaDir, entry.name);
|
|
59
75
|
if (entry.isDirectory()) {
|
package/lib/node/config.js
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getPackageRoot } from './paths.js';
|
|
4
|
+
|
|
5
|
+
export const META_DIR = 'assets/meta';
|
|
6
|
+
export const ICON_FOREGROUND = 'icon-foreground.png';
|
|
7
|
+
export const ICON_BACKGROUND = 'icon-background.png';
|
|
8
|
+
|
|
9
|
+
const ICON_FILES = [ICON_FOREGROUND, ICON_BACKGROUND];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Absolute path to a packaged Adaptfully placeholder icon.
|
|
13
|
+
* @param {string} filename
|
|
14
|
+
*/
|
|
15
|
+
export function resolvePlaceholderIconPath(filename) {
|
|
16
|
+
return path.join(getPackageRoot(), 'lib', 'assets', filename);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} [metaDir=META_DIR]
|
|
21
|
+
*/
|
|
22
|
+
export function resolveMetaIconPaths(metaDir = META_DIR) {
|
|
23
|
+
const root = path.resolve(metaDir);
|
|
24
|
+
return {
|
|
25
|
+
metaDir: root,
|
|
26
|
+
foreground: path.join(root, ICON_FOREGROUND),
|
|
27
|
+
background: path.join(root, ICON_BACKGROUND),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve layered icon paths for Wrapfully packaging. Warns (does not throw) when
|
|
33
|
+
* project icons are missing and falls back to Adaptfully placeholders.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ metaDir?: string, log?: (message: string) => void }} [options]
|
|
36
|
+
* @returns {{
|
|
37
|
+
* metaDir: string,
|
|
38
|
+
* foreground: string,
|
|
39
|
+
* background: string,
|
|
40
|
+
* missing: string[],
|
|
41
|
+
* usedPlaceholders: boolean,
|
|
42
|
+
* }}
|
|
43
|
+
*/
|
|
44
|
+
export function resolveMetaIcons(options = {}) {
|
|
45
|
+
const log = options.log ?? console.log;
|
|
46
|
+
const { metaDir, foreground, background } = resolveMetaIconPaths(options.metaDir);
|
|
47
|
+
/** @type {string[]} */
|
|
48
|
+
const missing = [];
|
|
49
|
+
let resolvedForeground = foreground;
|
|
50
|
+
let resolvedBackground = background;
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(foreground)) {
|
|
53
|
+
missing.push(ICON_FOREGROUND);
|
|
54
|
+
resolvedForeground = resolvePlaceholderIconPath(ICON_FOREGROUND);
|
|
55
|
+
}
|
|
56
|
+
if (!fs.existsSync(background)) {
|
|
57
|
+
missing.push(ICON_BACKGROUND);
|
|
58
|
+
resolvedBackground = resolvePlaceholderIconPath(ICON_BACKGROUND);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
log(
|
|
63
|
+
`adaptfully: WARNING missing layered icon(s) in ${metaDir}: ${missing.join(', ')}. `
|
|
64
|
+
+ 'Using Adaptfully placeholder icons for this build.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
metaDir,
|
|
70
|
+
foreground: resolvedForeground,
|
|
71
|
+
background: resolvedBackground,
|
|
72
|
+
missing,
|
|
73
|
+
usedPlaceholders: missing.length > 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Append layered icons to a zip under meta/. Uses project icons when present;
|
|
79
|
+
* otherwise warns and ships Adaptfully placeholders.
|
|
80
|
+
*
|
|
81
|
+
* @param {import('archiver').Archiver} zip
|
|
82
|
+
* @param {{ metaDir?: string, log?: (message: string) => void }} [options]
|
|
83
|
+
* @returns {ReturnType<typeof resolveMetaIcons>}
|
|
84
|
+
*/
|
|
85
|
+
export function appendMetaIcons(zip, options = {}) {
|
|
86
|
+
const resolved = resolveMetaIcons(options);
|
|
87
|
+
zip.file(resolved.foreground, { name: `meta/${ICON_FOREGROUND}` });
|
|
88
|
+
zip.file(resolved.background, { name: `meta/${ICON_BACKGROUND}` });
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} name
|
|
94
|
+
*/
|
|
95
|
+
export function isMetaIconFilename(name) {
|
|
96
|
+
return ICON_FILES.includes(name);
|
|
97
|
+
}
|
package/lib/node/index.js
CHANGED
|
@@ -2,7 +2,17 @@ export { buildOutputDir, clearStaleBuildExtract, resolveBuildArtifactDir } from
|
|
|
2
2
|
export { createArchive, createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
|
|
3
3
|
export { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
4
4
|
export { send } from './deploy.js';
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
appendMetaIcons,
|
|
7
|
+
ICON_BACKGROUND,
|
|
8
|
+
ICON_FOREGROUND,
|
|
9
|
+
isMetaIconFilename,
|
|
10
|
+
META_DIR,
|
|
11
|
+
resolveMetaIconPaths,
|
|
12
|
+
resolveMetaIcons,
|
|
13
|
+
resolvePlaceholderIconPath,
|
|
14
|
+
} from './icons.js';
|
|
15
|
+
export { adaptfullyFromCli, parseStageArgs, resolveWrapfullyRoute, runAdaptfullyStage } from './pipeline.js';
|
|
6
16
|
export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './prebuild.js';
|
|
7
17
|
export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
|
|
8
18
|
export {
|
|
@@ -223,6 +223,14 @@ export class Packager {
|
|
|
223
223
|
+ `Set config.platforms.${platformLabel}.packager to "electron".`,
|
|
224
224
|
);
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
if (this.usesPlugin('social-auth', { scope: 'all' }) && this.name !== 'capacitor') {
|
|
228
|
+
const platformLabel = this.platformKey ?? this.platforms[0] ?? 'unknown';
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Platform "${platformLabel}" uses social-auth but packager is "${this.name}". `
|
|
231
|
+
+ `Set config.platforms.${platformLabel}.packager to "capacitor".`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
needsGameConfig() {
|
|
@@ -1,5 +1,81 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { Packager } from './base.js';
|
|
2
4
|
|
|
5
|
+
const CAPACITOR_CSP = '<meta http-equiv="Content-Security-Policy" content="default-src * \'self\' data: blob: \'unsafe-inline\' \'unsafe-eval\'; style-src * \'self\' \'unsafe-inline\'; script-src * \'self\' \'unsafe-inline\' \'unsafe-eval\'; connect-src *; img-src * data: blob:; media-src * data: blob:; frame-src *;" />';
|
|
6
|
+
|
|
7
|
+
const CAPACITOR_VIEWPORT = '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string | null | undefined} platformKey
|
|
11
|
+
* @param {{ config?: { platforms?: Record<string, { socialLogin?: object }> } }} pkg
|
|
12
|
+
*/
|
|
13
|
+
export function resolvePlatformSocialLogin(platformKey, pkg) {
|
|
14
|
+
if (!platformKey) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return pkg.config?.platforms?.[platformKey]?.socialLogin;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} socialLogin
|
|
22
|
+
* @param {string} platformKey
|
|
23
|
+
*/
|
|
24
|
+
function normalizeSocialLoginConfig(socialLogin = {}, platformKey) {
|
|
25
|
+
const providers = socialLogin.providers ?? { google: true, apple: true };
|
|
26
|
+
const googleEnabled = providers.google !== false;
|
|
27
|
+
const appleEnabled = providers.apple !== false;
|
|
28
|
+
|
|
29
|
+
let defaultProvider = socialLogin.defaultProvider;
|
|
30
|
+
if (!defaultProvider) {
|
|
31
|
+
if (platformKey === 'ios' || platformKey === 'ios-dev' || platformKey === 'ios-sim') {
|
|
32
|
+
defaultProvider = appleEnabled ? 'apple' : 'google';
|
|
33
|
+
} else {
|
|
34
|
+
defaultProvider = googleEnabled ? 'google' : 'apple';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
providers: {
|
|
40
|
+
google: googleEnabled,
|
|
41
|
+
apple: appleEnabled,
|
|
42
|
+
facebook: providers.facebook === true,
|
|
43
|
+
twitter: providers.twitter === true,
|
|
44
|
+
},
|
|
45
|
+
google: socialLogin.google ?? {},
|
|
46
|
+
apple: socialLogin.apple ?? {},
|
|
47
|
+
defaultProvider,
|
|
48
|
+
platform: platformKey,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {object} config
|
|
54
|
+
*/
|
|
55
|
+
function validateSocialLoginConfig(config) {
|
|
56
|
+
const { providers, google, apple } = config;
|
|
57
|
+
|
|
58
|
+
if (!providers.google && !providers.apple) {
|
|
59
|
+
throw new Error('social-auth requires at least one of socialLogin.providers.google or .apple');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (providers.google) {
|
|
63
|
+
if (!google?.webClientId) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'social-auth with Google requires platforms.<key>.socialLogin.google.webClientId',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (providers.apple && config.platform?.startsWith('android')) {
|
|
71
|
+
if (!apple?.clientId) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'social-auth with Apple on Android requires platforms.<key>.socialLogin.apple.clientId',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
3
79
|
export class CapacitorPackager extends Packager {
|
|
4
80
|
/** @type {'capacitor'} */
|
|
5
81
|
static id = 'capacitor';
|
|
@@ -7,7 +83,57 @@ export class CapacitorPackager extends Packager {
|
|
|
7
83
|
/** @type {string[]} */
|
|
8
84
|
static defaultPlatforms = ['ios', 'android'];
|
|
9
85
|
|
|
86
|
+
validate() {
|
|
87
|
+
super.validate();
|
|
88
|
+
|
|
89
|
+
if (this.usesPlugin('social-auth')) {
|
|
90
|
+
const socialLogin = resolvePlatformSocialLogin(this.platformKey, this.pkg);
|
|
91
|
+
if (!socialLogin && !this.pkg.config?.socialLogin) {
|
|
92
|
+
const platformLabel = this.platformKey ?? this.platforms.join(', ');
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Platform "${platformLabel}" uses social-auth but platforms.${platformLabel}.socialLogin is not set.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const config = normalizeSocialLoginConfig(
|
|
99
|
+
socialLogin ?? this.pkg.config?.socialLogin,
|
|
100
|
+
this.platformKey ?? 'android',
|
|
101
|
+
);
|
|
102
|
+
validateSocialLoginConfig(config);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
10
106
|
needsGameConfig() {
|
|
11
107
|
return true;
|
|
12
108
|
}
|
|
109
|
+
|
|
110
|
+
/** @param {string} dest */
|
|
111
|
+
applyTemplates(dest) {
|
|
112
|
+
if (this.usesPlugin('social-auth') && this.platformKey) {
|
|
113
|
+
const socialLogin = resolvePlatformSocialLogin(this.platformKey, this.pkg)
|
|
114
|
+
?? this.pkg.config?.socialLogin
|
|
115
|
+
?? {};
|
|
116
|
+
const config = normalizeSocialLoginConfig(socialLogin, this.platformKey);
|
|
117
|
+
const content = `window.__ADAPTFULLY_SOCIAL_LOGIN__ = ${JSON.stringify(config, null, 4)};\n`;
|
|
118
|
+
fs.writeFileSync(path.join(dest, 'social-login-config.js'), content);
|
|
119
|
+
this.log('adaptfully: write social-login-config.js (social-auth)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
super.applyTemplates(dest);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
buildHtmlInjection() {
|
|
126
|
+
const headExtras = [CAPACITOR_CSP, CAPACITOR_VIEWPORT];
|
|
127
|
+
const bodyScripts = [];
|
|
128
|
+
|
|
129
|
+
if (this.usesPlugin('social-auth')) {
|
|
130
|
+
bodyScripts.push('<script src="social-login-config.js"></script>');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this.needsGameConfig()) {
|
|
134
|
+
bodyScripts.push('<script src="game-config.js"></script>');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return this.formatHtmlInjection(headExtras, bodyScripts);
|
|
138
|
+
}
|
|
13
139
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { CapacitorPackager } from './capacitor.js';
|
|
2
2
|
import { CordovaPackager } from './cordova.js';
|
|
3
|
-
import { ElectronPackager
|
|
3
|
+
import { ElectronPackager } from './electron.js';
|
|
4
4
|
import { Packager, VALID_PACKAGERS } from './base.js';
|
|
5
5
|
import { WebPackager } from './web.js';
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
7
|
+
export { CapacitorPackager, resolvePlatformSocialLogin } from './capacitor.js';
|
|
8
|
+
export { CordovaPackager } from './cordova.js';
|
|
9
9
|
export {
|
|
10
10
|
ElectronPackager,
|
|
11
11
|
buildElectronMain,
|
|
12
12
|
buildElectronPreload,
|
|
13
13
|
resolvePlatformSteamId,
|
|
14
14
|
} from './electron.js';
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
15
|
+
export { Packager, VALID_PACKAGERS } from './base.js';
|
|
16
|
+
export { WebPackager } from './web.js';
|
|
17
17
|
|
|
18
18
|
/** @typedef {import('./base.js').PackagerName} PackagerName */
|
|
19
19
|
|
|
@@ -72,6 +72,13 @@ export function usesSteamAuth(registrations) {
|
|
|
72
72
|
return Object.values(registrations).includes('steam-auth');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @param {Record<string, string>} registrations
|
|
77
|
+
*/
|
|
78
|
+
export function usesSocialAuth(registrations) {
|
|
79
|
+
return Object.values(registrations).includes('social-auth');
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
/**
|
|
76
83
|
* @param {string} dest
|
|
77
84
|
* @param {string} platformKey
|
package/lib/node/pipeline.js
CHANGED
|
@@ -14,6 +14,21 @@ import { steamAuthFromCli } from './steam-auth.js';
|
|
|
14
14
|
|
|
15
15
|
const VALID_STAGES = new Set(['prebuild', 'build', 'deploy', 'release']);
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Wrapfully URL path segment for build/release.
|
|
19
|
+
* Single-target platforms post to the target route (`android`, `ios`, …) so OS relay
|
|
20
|
+
* and long-standing target routes work; multi-target platforms post to the family
|
|
21
|
+
* (`electron`, …).
|
|
22
|
+
*
|
|
23
|
+
* @param {{ family: string, targets: string[] }} buildSpec
|
|
24
|
+
*/
|
|
25
|
+
export function resolveWrapfullyRoute(buildSpec) {
|
|
26
|
+
if (buildSpec.targets.length === 1) {
|
|
27
|
+
return buildSpec.targets[0];
|
|
28
|
+
}
|
|
29
|
+
return buildSpec.family;
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
/**
|
|
18
33
|
* @param {AdaptfullyStage} stage
|
|
19
34
|
* @param {string} platformKey
|
|
@@ -69,10 +84,15 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
69
84
|
/** @type {{ deploymentKey: string, stage: string, family: string }[]} */
|
|
70
85
|
const sent = [];
|
|
71
86
|
|
|
87
|
+
const wrapfullyRoute = resolveWrapfullyRoute(buildSpec);
|
|
88
|
+
|
|
72
89
|
if (stage === 'release') {
|
|
73
90
|
const deploymentDirs = deployments.map((key) => resolvePublishDir(key));
|
|
74
91
|
|
|
75
|
-
log(
|
|
92
|
+
log(
|
|
93
|
+
`adaptfully: ${stage} ${platformKey} → ${buildSpec.family} `
|
|
94
|
+
+ `(${buildSpec.targets.join(', ')}) via /${wrapfullyRoute}/release`,
|
|
95
|
+
);
|
|
76
96
|
|
|
77
97
|
log(
|
|
78
98
|
`adaptfully: release deployments → ${
|
|
@@ -85,7 +105,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
85
105
|
contents,
|
|
86
106
|
options.server,
|
|
87
107
|
'release',
|
|
88
|
-
|
|
108
|
+
wrapfullyRoute,
|
|
89
109
|
prebuiltDir,
|
|
90
110
|
pkg,
|
|
91
111
|
options.mode ?? 'extract',
|
|
@@ -107,7 +127,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
107
127
|
log(
|
|
108
128
|
`adaptfully: ${stage} ${platformKey} → `
|
|
109
129
|
+ `${deploymentKey === 'zip' ? 'artifact zip' : `deployment "${deploymentKey}"`} `
|
|
110
|
-
+ `via Wrapfully ${buildSpec.family}`,
|
|
130
|
+
+ `via Wrapfully /${wrapfullyRoute}/${wrapStage} (${buildSpec.family})`,
|
|
111
131
|
);
|
|
112
132
|
|
|
113
133
|
await send(
|
|
@@ -115,7 +135,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
115
135
|
contents,
|
|
116
136
|
options.server,
|
|
117
137
|
wrapStage,
|
|
118
|
-
|
|
138
|
+
wrapfullyRoute,
|
|
119
139
|
prebuiltDir,
|
|
120
140
|
pkg,
|
|
121
141
|
options.mode ?? 'extract',
|
|
@@ -158,9 +178,9 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
158
178
|
+ '\n'
|
|
159
179
|
+ 'Stages:\n'
|
|
160
180
|
+ ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
|
|
161
|
-
+ ' build prebuild + POST /{family}/build (zip artifact only)\n'
|
|
181
|
+
+ ' build prebuild + POST /{target|family}/build (zip artifact only)\n'
|
|
162
182
|
+ ' deploy POST /deploy/{key} with prior artifact + deployment credentials\n'
|
|
163
|
-
+ ' release prebuild + POST /{family}/release (build + configured deployments)\n'
|
|
183
|
+
+ ' release prebuild + POST /{target|family}/release (build + configured deployments)\n'
|
|
164
184
|
+ '\n'
|
|
165
185
|
+ ' --deployment <key> Target a single named deployment.\n'
|
|
166
186
|
+ ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
|
|
@@ -22,6 +22,10 @@ export const STANDARD_PLUGINS = {
|
|
|
22
22
|
scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/steam-auth.js'],
|
|
23
23
|
registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Steam);`,
|
|
24
24
|
},
|
|
25
|
+
'social-auth': {
|
|
26
|
+
scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/social-auth.js'],
|
|
27
|
+
registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Social);`,
|
|
28
|
+
},
|
|
25
29
|
'dev-auth': {
|
|
26
30
|
scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/dev-auth.js'],
|
|
27
31
|
registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Dev);`,
|
|
@@ -52,10 +56,6 @@ export const DEFAULT_BUILDER_PLATFORMS = {
|
|
|
52
56
|
'ios-dev': 'ios',
|
|
53
57
|
'ios-sim': 'ios',
|
|
54
58
|
webapp: 'web',
|
|
55
|
-
cordova: 'cordova',
|
|
56
|
-
'cordova-dev': 'cordova',
|
|
57
|
-
apple: 'apple',
|
|
58
|
-
'apple-dev': 'apple',
|
|
59
59
|
uwp: 'uwp',
|
|
60
60
|
};
|
|
61
61
|
|
|
@@ -340,7 +340,7 @@ export function resolvePublishDir(deploymentKey, metaDir = 'assets/meta') {
|
|
|
340
340
|
const ELECTRON_TARGETS = new Set([
|
|
341
341
|
'win', 'win-dev', 'mac', 'mac-dev', 'linux', 'linux-dev',
|
|
342
342
|
]);
|
|
343
|
-
const
|
|
343
|
+
const MOBILE_TARGETS = new Set([
|
|
344
344
|
'android', 'android-dev', 'ios', 'ios-dev', 'ios-sim',
|
|
345
345
|
]);
|
|
346
346
|
|
|
@@ -352,8 +352,8 @@ export function resolveFamilyFromTarget(target) {
|
|
|
352
352
|
if (ELECTRON_TARGETS.has(target)) {
|
|
353
353
|
return 'electron';
|
|
354
354
|
}
|
|
355
|
-
if (
|
|
356
|
-
return '
|
|
355
|
+
if (MOBILE_TARGETS.has(target)) {
|
|
356
|
+
return 'capacitor';
|
|
357
357
|
}
|
|
358
358
|
if (target === 'pwa') {
|
|
359
359
|
return 'pwa';
|
|
@@ -379,9 +379,12 @@ export function resolveFamily(targets, packager) {
|
|
|
379
379
|
if (packager === 'electron') {
|
|
380
380
|
return 'electron';
|
|
381
381
|
}
|
|
382
|
-
if (packager === 'cordova'
|
|
382
|
+
if (packager === 'cordova') {
|
|
383
383
|
return 'cordova';
|
|
384
384
|
}
|
|
385
|
+
if (packager === 'capacitor') {
|
|
386
|
+
return 'capacitor';
|
|
387
|
+
}
|
|
385
388
|
|
|
386
389
|
const families = [...new Set(targets.map((target) => resolveFamilyFromTarget(target)).filter(Boolean))];
|
|
387
390
|
|
|
@@ -396,20 +399,35 @@ export function resolveFamily(targets, packager) {
|
|
|
396
399
|
throw new Error(`Cannot resolve builder family for targets [${targets.join(', ')}]`);
|
|
397
400
|
}
|
|
398
401
|
|
|
402
|
+
/**
|
|
403
|
+
* @param {object | undefined} platform
|
|
404
|
+
* @returns {boolean}
|
|
405
|
+
*/
|
|
406
|
+
function platformUsesSocialAuth(platform) {
|
|
407
|
+
if (!platform?.registrations) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
return Object.values(platform.registrations).includes('social-auth');
|
|
411
|
+
}
|
|
412
|
+
|
|
399
413
|
/**
|
|
400
414
|
* @param {string} platformKey
|
|
401
|
-
* @param {{ config?: { platforms?: Record<string, { builder?: string | string[], packager?: string, steamworks?: boolean, deployments?: string[] }> } }} pkg
|
|
415
|
+
* @param {{ config?: { platforms?: Record<string, { builder?: string | string[], packager?: string, steamworks?: boolean, socialLogin?: boolean | object, registrations?: Record<string, string>, deployments?: string[] }> } }} pkg
|
|
402
416
|
*/
|
|
403
417
|
export function resolveBuildSpec(platformKey, pkg) {
|
|
404
418
|
const platform = pkg.config?.platforms?.[platformKey];
|
|
405
419
|
const builder = platform?.builder ?? (platformKey === 'web' ? 'webapp' : platformKey);
|
|
406
420
|
const targets = Array.isArray(builder) ? [...builder] : [builder];
|
|
421
|
+
const socialLogin = platform?.socialLogin === true
|
|
422
|
+
|| (typeof platform?.socialLogin === 'object' && platform.socialLogin != null)
|
|
423
|
+
|| platformUsesSocialAuth(platform);
|
|
407
424
|
|
|
408
425
|
return {
|
|
409
426
|
family: resolveFamily(targets, platform?.packager),
|
|
410
427
|
targets,
|
|
411
428
|
platformKey,
|
|
412
429
|
steamworks: platform?.steamworks === true,
|
|
430
|
+
socialLogin,
|
|
413
431
|
deployments: resolveDeploymentsForPlatform(platformKey, pkg),
|
|
414
432
|
};
|
|
415
433
|
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Social auth via @capgo/capacitor-social-login.
|
|
5
|
+
* Adaptfully prebuild writes social-login-config.js and Wrapfully installs the Capgo plugin
|
|
6
|
+
* when packager is "capacitor" and registrations.auth is "social-auth".
|
|
7
|
+
*/
|
|
8
|
+
(function registerSocialAuth(ns) {
|
|
9
|
+
const { configValue, getStorage } = ns.auth.helpers;
|
|
10
|
+
|
|
11
|
+
const DEFAULT_AUTO_LOGIN_KEY = 'lastLoggedIn';
|
|
12
|
+
const READY_POLL_MS = 50;
|
|
13
|
+
const READY_TIMEOUT_MS = 10000;
|
|
14
|
+
|
|
15
|
+
function readSocialLoginConfig() {
|
|
16
|
+
return window.__ADAPTFULLY_SOCIAL_LOGIN__ || {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveSocialLoginPlugin() {
|
|
20
|
+
const plugins = window.Capacitor?.Plugins;
|
|
21
|
+
if (plugins?.SocialLogin) {
|
|
22
|
+
return plugins.SocialLogin;
|
|
23
|
+
}
|
|
24
|
+
if (window.SocialLogin) {
|
|
25
|
+
return window.SocialLogin;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapLoginResult(result) {
|
|
31
|
+
const resultResult = result?.result ?? result;
|
|
32
|
+
const profile = resultResult?.profile
|
|
33
|
+
|| resultResult?.user
|
|
34
|
+
|| resultResult
|
|
35
|
+
|| {};
|
|
36
|
+
const id = String(
|
|
37
|
+
profile.id
|
|
38
|
+
|| profile.user
|
|
39
|
+
|| profile.sub
|
|
40
|
+
|| resultResult?.accessToken?.userId
|
|
41
|
+
|| resultResult?.idToken
|
|
42
|
+
|| '',
|
|
43
|
+
);
|
|
44
|
+
const email = String(profile.email || '');
|
|
45
|
+
|
|
46
|
+
if (!id && !email) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
id: id || email,
|
|
52
|
+
email,
|
|
53
|
+
displayName: profile.name || profile.givenName || '',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class SocialAuthPlugin {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.name = 'social';
|
|
60
|
+
this.user = null;
|
|
61
|
+
this.authenticated = false;
|
|
62
|
+
this.online = false;
|
|
63
|
+
this.#plugin = null;
|
|
64
|
+
this.#provider = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @type {object | null} */
|
|
68
|
+
#plugin;
|
|
69
|
+
|
|
70
|
+
/** @type {string | null} */
|
|
71
|
+
#provider;
|
|
72
|
+
|
|
73
|
+
supportsAutoLogin() {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#autoLoginStorageKey() {
|
|
78
|
+
return configValue('autoLoginStorageKey', DEFAULT_AUTO_LOGIN_KEY);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#config() {
|
|
82
|
+
return readSocialLoginConfig();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#resolveDefaultProvider() {
|
|
86
|
+
const config = this.#config();
|
|
87
|
+
if (config.defaultProvider) {
|
|
88
|
+
return config.defaultProvider;
|
|
89
|
+
}
|
|
90
|
+
const platform = config.platform || window.gameConfig?.platform || '';
|
|
91
|
+
if (String(platform).startsWith('ios')) {
|
|
92
|
+
return config.providers?.apple === false ? 'google' : 'apple';
|
|
93
|
+
}
|
|
94
|
+
return config.providers?.google === false ? 'apple' : 'google';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#buildInitializeOptions() {
|
|
98
|
+
const config = this.#config();
|
|
99
|
+
const options = {};
|
|
100
|
+
|
|
101
|
+
if (config.providers?.google !== false) {
|
|
102
|
+
options.google = {
|
|
103
|
+
webClientId: config.google?.webClientId
|
|
104
|
+
|| configValue('googleClientId', ''),
|
|
105
|
+
iOSClientId: config.google?.iOSClientId,
|
|
106
|
+
iOSServerClientId: config.google?.iOSServerClientId
|
|
107
|
+
|| config.google?.webClientId,
|
|
108
|
+
mode: config.google?.mode || 'online',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (config.providers?.apple !== false) {
|
|
113
|
+
options.apple = {
|
|
114
|
+
clientId: config.apple?.clientId || '',
|
|
115
|
+
redirectUrl: config.apple?.redirectUrl,
|
|
116
|
+
useProperTokenExchange: config.apple?.useProperTokenExchange !== false,
|
|
117
|
+
useBroadcastChannel: config.apple?.useBroadcastChannel !== false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return options;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#persistLogin(user) {
|
|
125
|
+
const storage = getStorage();
|
|
126
|
+
storage?.set(this.#autoLoginStorageKey(), user.id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#applyIdentity(identity) {
|
|
130
|
+
if (!identity?.id) {
|
|
131
|
+
this.user = null;
|
|
132
|
+
this.authenticated = false;
|
|
133
|
+
this.online = false;
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.user = {
|
|
138
|
+
id: identity.id,
|
|
139
|
+
email: identity.email || '',
|
|
140
|
+
displayName: identity.displayName || '',
|
|
141
|
+
};
|
|
142
|
+
this.authenticated = true;
|
|
143
|
+
this.online = true;
|
|
144
|
+
this.#persistLogin(this.user);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#complete(callback) {
|
|
149
|
+
callback({
|
|
150
|
+
authenticated: this.authenticated,
|
|
151
|
+
user: this.getUser(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
whenReady(done) {
|
|
156
|
+
const started = Date.now();
|
|
157
|
+
const timeoutMs = Number(configValue('socialReadyTimeoutMs', READY_TIMEOUT_MS))
|
|
158
|
+
|| READY_TIMEOUT_MS;
|
|
159
|
+
|
|
160
|
+
const finish = async () => {
|
|
161
|
+
const plugin = resolveSocialLoginPlugin();
|
|
162
|
+
if (!plugin) {
|
|
163
|
+
if (Date.now() - started >= timeoutMs) {
|
|
164
|
+
console.warn('[adaptfully social-auth] Capgo SocialLogin plugin not available');
|
|
165
|
+
this.online = false;
|
|
166
|
+
done({ error: 'SocialLogin plugin not available' });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
window.setTimeout(finish, READY_POLL_MS);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.#plugin = plugin;
|
|
174
|
+
this.#provider = this.#resolveDefaultProvider();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await plugin.initialize(this.#buildInitializeOptions());
|
|
178
|
+
this.online = true;
|
|
179
|
+
done();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error('[adaptfully social-auth] initialize failed:', err);
|
|
182
|
+
this.online = false;
|
|
183
|
+
done({ error: err?.message || 'SocialLogin initialize failed' });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
finish();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
login(callback) {
|
|
191
|
+
const plugin = this.#plugin ?? resolveSocialLoginPlugin();
|
|
192
|
+
const provider = this.#provider ?? this.#resolveDefaultProvider();
|
|
193
|
+
|
|
194
|
+
if (!plugin) {
|
|
195
|
+
console.warn('[adaptfully social-auth] login: plugin missing');
|
|
196
|
+
this.#complete(callback);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
plugin.login({
|
|
201
|
+
provider,
|
|
202
|
+
options: {
|
|
203
|
+
scopes: provider === 'google'
|
|
204
|
+
? ['email', 'profile']
|
|
205
|
+
: ['email', 'name'],
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
.then((result) => {
|
|
209
|
+
if (!this.#applyIdentity(mapLoginResult(result))) {
|
|
210
|
+
console.warn('[adaptfully social-auth] login: could not map identity', result);
|
|
211
|
+
}
|
|
212
|
+
this.#complete(callback);
|
|
213
|
+
})
|
|
214
|
+
.catch((err) => {
|
|
215
|
+
console.error('[adaptfully social-auth] login failed:', err);
|
|
216
|
+
this.user = null;
|
|
217
|
+
this.authenticated = false;
|
|
218
|
+
this.online = false;
|
|
219
|
+
this.#complete(callback);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
autoLogin(callback) {
|
|
224
|
+
const plugin = this.#plugin ?? resolveSocialLoginPlugin();
|
|
225
|
+
const provider = this.#provider ?? this.#resolveDefaultProvider();
|
|
226
|
+
const storage = getStorage();
|
|
227
|
+
const lastId = storage?.get?.(this.#autoLoginStorageKey());
|
|
228
|
+
|
|
229
|
+
if (!plugin || typeof plugin.isLoggedIn !== 'function') {
|
|
230
|
+
if (lastId) {
|
|
231
|
+
this.#applyIdentity({ id: String(lastId), email: '' });
|
|
232
|
+
}
|
|
233
|
+
this.#complete(callback);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
plugin.isLoggedIn({ provider })
|
|
238
|
+
.then(async (status) => {
|
|
239
|
+
if (!status?.isLoggedIn) {
|
|
240
|
+
this.user = null;
|
|
241
|
+
this.authenticated = false;
|
|
242
|
+
this.#complete(callback);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (typeof plugin.getAuthorizationCode === 'function') {
|
|
247
|
+
try {
|
|
248
|
+
const auth = await plugin.getAuthorizationCode({ provider });
|
|
249
|
+
const identity = mapLoginResult(auth) || (lastId
|
|
250
|
+
? { id: String(lastId), email: '' }
|
|
251
|
+
: null);
|
|
252
|
+
this.#applyIdentity(identity);
|
|
253
|
+
this.#complete(callback);
|
|
254
|
+
return;
|
|
255
|
+
} catch {
|
|
256
|
+
// fall through to lastId
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (lastId) {
|
|
261
|
+
this.#applyIdentity({ id: String(lastId), email: '' });
|
|
262
|
+
}
|
|
263
|
+
this.#complete(callback);
|
|
264
|
+
})
|
|
265
|
+
.catch((err) => {
|
|
266
|
+
console.warn('[adaptfully social-auth] autoLogin failed:', err);
|
|
267
|
+
this.#complete(callback);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
logout(callback) {
|
|
272
|
+
const plugin = this.#plugin ?? resolveSocialLoginPlugin();
|
|
273
|
+
const provider = this.#provider ?? this.#resolveDefaultProvider();
|
|
274
|
+
const storage = getStorage();
|
|
275
|
+
|
|
276
|
+
const clear = () => {
|
|
277
|
+
this.user = null;
|
|
278
|
+
this.authenticated = false;
|
|
279
|
+
this.online = false;
|
|
280
|
+
storage?.remove(this.#autoLoginStorageKey());
|
|
281
|
+
callback();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (!plugin || typeof plugin.logout !== 'function') {
|
|
285
|
+
clear();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
plugin.logout({ provider })
|
|
290
|
+
.then(clear)
|
|
291
|
+
.catch((err) => {
|
|
292
|
+
console.warn('[adaptfully social-auth] logout failed:', err);
|
|
293
|
+
clear();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
getUser() {
|
|
298
|
+
if (!this.authenticated || !this.user) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
id: this.user.id,
|
|
303
|
+
email: this.user.email || '',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
isAuthenticated() {
|
|
308
|
+
return !!this.authenticated;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
ns.auth.Social = () => new SocialAuthPlugin();
|
|
313
|
+
}(window.adaptfully));
|