@mpgd/cli 0.55.0 → 0.57.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/dist/capacitor-shell-starter.js +130 -3
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +2091 -220
- package/dist/notifications-starter.d.ts +29 -2
- package/dist/notifications-starter.js +63 -4
- package/package.json +18 -18
- package/templates/lite-overlay/agent/acceptance.md +1 -0
- package/templates/lite-overlay/agent/brief.md +1 -0
- package/templates/notifications-overlay/common/docs/NOTIFICATIONS.md +91 -3
- package/templates/notifications-overlay/common/src/platform/notificationBackend.ts +113 -3
- package/templates/notifications-overlay/common/src/platform/notifications.ts +19 -1
- package/templates/phaser-game/agent/acceptance.md +10 -0
- package/templates/phaser-game/agent/brief.md +12 -0
- package/templates/phaser-game/apps/target-devvit/src/server/bridge.ts +37 -0
- package/templates/phaser-game/src/env.d.ts +5 -0
- package/templates/phaser-game/src/platform/developmentServerSession.ts +104 -0
- package/templates/phaser-game/src/platform/serverSession.ts +49 -26
|
@@ -19,4 +19,9 @@ interface ImportMetaEnv {
|
|
|
19
19
|
readonly VITE_MPGD_GAME_SERVICES_URL?: string;
|
|
20
20
|
readonly VITE_MPGD_GAME_SERVICES_TARGET?: string;
|
|
21
21
|
readonly VITE_MPGD_GAME_SERVICES_TRANSPORT?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Local development only: `<playerId>` or `<playerId>.<installationId>`
|
|
24
|
+
* for an unauthenticated notifications session (see `serverSession.ts`).
|
|
25
|
+
*/
|
|
26
|
+
readonly VITE_MPGD_DEV_NOTIFICATION_SESSION?: string;
|
|
22
27
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createDevelopmentServerSession, type DevelopmentServerSession } from '@mpgd/game-services';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local development only: an unauthenticated session for a game-services
|
|
5
|
+
* Worker run with `MPGD_ALLOW_INSECURE_DEVELOPMENT_NOTIFICATION_SESSIONS = "true"`
|
|
6
|
+
* (see docs/NOTIFICATIONS.md, "Try it locally"). On only when the Vite dev
|
|
7
|
+
* server runs (`dev`, `import.meta.env.DEV`), the mode is `development`, and
|
|
8
|
+
* `VITE_MPGD_DEV_NOTIFICATION_SESSION` is `<playerId>` or
|
|
9
|
+
* `<playerId>.<installationId>` (each 1 to 64 letters, digits, `_`, or `-`;
|
|
10
|
+
* the installation defaults to `dev-installation`). The session id is
|
|
11
|
+
* `dev-<installationId>`. Anyone can write such a header, so the Worker
|
|
12
|
+
* refuses the flag with its D1 store. Otherwise `undefined`; an invalid
|
|
13
|
+
* value logs why.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveStarterDevelopmentServerSession(env: {
|
|
16
|
+
readonly dev: boolean;
|
|
17
|
+
readonly mode: string;
|
|
18
|
+
readonly value: string | undefined;
|
|
19
|
+
}): DevelopmentServerSession | undefined {
|
|
20
|
+
const value = env.value?.trim();
|
|
21
|
+
// Only the development mode: a staging or release build that runs with
|
|
22
|
+
// NODE_ENV=development has `dev` set too.
|
|
23
|
+
if (!env.dev || env.mode !== 'development' || value === undefined || value === '') {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const [playerId = '', installationId = 'dev-installation', ...rest] = value.split('.');
|
|
27
|
+
try {
|
|
28
|
+
if (rest.length > 0) {
|
|
29
|
+
throw new Error('Expected <playerId> or <playerId>.<installationId>.');
|
|
30
|
+
}
|
|
31
|
+
const session = createDevelopmentServerSession({ mode: env.mode, playerId, installationId });
|
|
32
|
+
console.warn(
|
|
33
|
+
`[game-services] VITE_MPGD_DEV_NOTIFICATION_SESSION: signed in as ${playerId} with an `
|
|
34
|
+
+ 'unauthenticated development session. Local development only.',
|
|
35
|
+
);
|
|
36
|
+
return session;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[game-services] VITE_MPGD_DEV_NOTIFICATION_SESSION is ignored.', error);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What `serverSession.ts` hands out, kept apart from `import.meta.env` so it
|
|
45
|
+
* can be tested: the game's own session once it called `set` (even with
|
|
46
|
+
* `undefined`), else the development session, read once. `once` marks the
|
|
47
|
+
* reader that keeps what it reads, the commerce client, which only ever
|
|
48
|
+
* gets the game's own session: the development session is for the
|
|
49
|
+
* notifications Worker, and a commerce backend with ingress authentication
|
|
50
|
+
* would refuse its header. A `set` that changes the session after a reader
|
|
51
|
+
* kept the old one warns: after the commerce client read it, or after the
|
|
52
|
+
* development session was handed out (a notifications backend keeps it).
|
|
53
|
+
*/
|
|
54
|
+
export function createStarterServerSessionSlot<Session extends object>(input: {
|
|
55
|
+
/** The development session; called at most once. */
|
|
56
|
+
readonly development: () => Session | undefined;
|
|
57
|
+
readonly warn: (message: string) => void;
|
|
58
|
+
}): {
|
|
59
|
+
resolve(options?: { readonly once?: boolean }): Session | undefined;
|
|
60
|
+
set(session: Session | undefined): void;
|
|
61
|
+
} {
|
|
62
|
+
let session: Session | undefined;
|
|
63
|
+
let setByGame = false;
|
|
64
|
+
let readOnce = false;
|
|
65
|
+
let development: Session | null | undefined;
|
|
66
|
+
let developmentHandedOut = false;
|
|
67
|
+
return {
|
|
68
|
+
resolve(options = {}) {
|
|
69
|
+
if (options.once === true) {
|
|
70
|
+
readOnce = true;
|
|
71
|
+
}
|
|
72
|
+
// The commerce client never gets the development session.
|
|
73
|
+
if (setByGame || options.once === true) {
|
|
74
|
+
return session;
|
|
75
|
+
}
|
|
76
|
+
if (development === undefined) {
|
|
77
|
+
development = input.development() ?? null;
|
|
78
|
+
}
|
|
79
|
+
if (development === null) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
developmentHandedOut = true;
|
|
83
|
+
return development;
|
|
84
|
+
},
|
|
85
|
+
set(next) {
|
|
86
|
+
if (developmentHandedOut && !setByGame) {
|
|
87
|
+
// Even `undefined`: a notifications backend that kept the development session keeps sending its header.
|
|
88
|
+
input.warn(
|
|
89
|
+
'[game-services] setStarterServerSession ran after the development session '
|
|
90
|
+
+ '(VITE_MPGD_DEV_NOTIFICATION_SESSION) was in use; a notifications backend created '
|
|
91
|
+
+ 'already keeps it. Unset the variable, or call setStarterServerSession before bootstrap().',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (readOnce && next !== undefined && next !== session) {
|
|
95
|
+
input.warn(
|
|
96
|
+
'[game-services] setStarterServerSession ran after the commerce client was created; '
|
|
97
|
+
+ "it does not send this session's header. Call it before bootstrap().",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
session = next;
|
|
101
|
+
setByGame = true;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -1,19 +1,37 @@
|
|
|
1
1
|
import type { GameServicesHeaderResolver } from '@mpgd/game-services';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
createStarterServerSessionSlot,
|
|
5
|
+
resolveStarterDevelopmentServerSession,
|
|
6
|
+
} from './developmentServerSession';
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* The game's server-backed session: who is signed in to the game backend,
|
|
5
10
|
* and the `Authorization` header for that session. The starter has none, so
|
|
6
11
|
* `resolveStarterServerSession` returns `undefined`. Set yours with
|
|
7
|
-
* `setStarterServerSession
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* `setStarterServerSession`. The commerce client then sends its header. In
|
|
13
|
+
* a game created with `--providers notifications`, the notifications
|
|
14
|
+
* runtime uses the same session and header for the game-services Worker's
|
|
15
|
+
* notification routes.
|
|
16
|
+
*
|
|
17
|
+
* With a guest-session backend, `createGuestServerSession` from
|
|
18
|
+
* `@mpgd/game-services` reads a `GuestSessionCoordinator` as this session:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const guest = createGuestSessionCoordinator({ backend, credentials, installationId });
|
|
22
|
+
* setStarterServerSession(createGuestServerSession(guest));
|
|
23
|
+
* void guest.start();
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* `sessionId` must be the stable session-family id: the same across token
|
|
27
|
+
* refreshes, and the session your identity service answers to the Worker's
|
|
28
|
+
* `authenticateNotificationSession` for this session's header. Do not pass
|
|
29
|
+
* a per-token or rotating id: `createGuestServerSession` answers the
|
|
30
|
+
* coordinator's `sessionFamilyId`, never a view's `sessionId`, which may
|
|
31
|
+
* rotate on refresh. With another session holder, pass its family id.
|
|
11
32
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* for this session's header, the same across token refreshes. Do not pass a
|
|
15
|
-
* per-token or rotating id: a `GuestSessionCoordinator` view's `sessionId`
|
|
16
|
-
* may rotate on refresh, so it is not that id.
|
|
33
|
+
* For local development without an identity service, see
|
|
34
|
+
* `developmentServerSession.ts`.
|
|
17
35
|
*/
|
|
18
36
|
export interface StarterServerSession {
|
|
19
37
|
/**
|
|
@@ -29,22 +47,31 @@ export interface StarterServerSession {
|
|
|
29
47
|
readonly getHeaders: GameServicesHeaderResolver;
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
/** The game's session, or the development one (see `developmentServerSession.ts`). */
|
|
51
|
+
const slot = createStarterServerSessionSlot<StarterServerSession>({
|
|
52
|
+
// `import.meta.env.DEV` is false in a normal `vite build`, which drops this branch.
|
|
53
|
+
development: () =>
|
|
54
|
+
import.meta.env.DEV
|
|
55
|
+
? resolveStarterDevelopmentServerSession({
|
|
56
|
+
dev: import.meta.env.DEV,
|
|
57
|
+
mode: import.meta.env.MODE,
|
|
58
|
+
value: import.meta.env.VITE_MPGD_DEV_NOTIFICATION_SESSION,
|
|
59
|
+
})
|
|
60
|
+
: undefined,
|
|
61
|
+
warn: (message) => console.warn(message),
|
|
62
|
+
});
|
|
35
63
|
|
|
36
64
|
/**
|
|
37
65
|
* The game's server-backed session; `undefined` until the game sets one with
|
|
38
|
-
* `setStarterServerSession
|
|
39
|
-
*
|
|
66
|
+
* `setStarterServerSession`, unless a development session is on (see
|
|
67
|
+
* `developmentServerSession.ts`). `once` marks the reader that keeps what
|
|
68
|
+
* it reads, the commerce client, so a later change warns; it gets only the
|
|
69
|
+
* game's own session, never the development one.
|
|
40
70
|
*/
|
|
41
71
|
export function resolveStarterServerSession(
|
|
42
72
|
options: { readonly once?: boolean } = {},
|
|
43
73
|
): StarterServerSession | undefined {
|
|
44
|
-
|
|
45
|
-
readOnce = true;
|
|
46
|
-
}
|
|
47
|
-
return serverSession;
|
|
74
|
+
return slot.resolve(options);
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
/**
|
|
@@ -53,14 +80,10 @@ export function resolveStarterServerSession(
|
|
|
53
80
|
* notification settings and opens read it until a backend exists. Its
|
|
54
81
|
* `current()` may answer `undefined` until the player signs in. A session
|
|
55
82
|
* set after the commerce client was created does not reach that client,
|
|
56
|
-
* which this warns about.
|
|
83
|
+
* which this warns about. Any call, `undefined` included, turns the
|
|
84
|
+
* development session off; the first call after the development session
|
|
85
|
+
* was handed out warns, `undefined` included.
|
|
57
86
|
*/
|
|
58
87
|
export function setStarterServerSession(session: StarterServerSession | undefined): void {
|
|
59
|
-
|
|
60
|
-
console.warn(
|
|
61
|
-
'[game-services] setStarterServerSession ran after the commerce client was created; '
|
|
62
|
-
+ "it does not send this session's header. Call it before bootstrap().",
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
serverSession = session;
|
|
88
|
+
slot.set(session);
|
|
66
89
|
}
|