@arcadible/sdk 1.5.0-next.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/dist/host.d.ts +1 -1
- package/dist/host.js +17 -7
- package/dist/system/client.d.ts +6 -1
- package/dist/system/rules.d.ts +14 -6
- package/dist/system/rules.js +41 -17
- package/dist/system/server.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,20 +7,26 @@
|
|
|
7
7
|
<h1 align="center">Arcadible SDK</h1>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
|
|
10
|
+
How your game works with Arcadible.
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://developer.arcadible.com/references/arcadible-sdk">Docs</a> ·
|
|
15
15
|
<a href="https://developer.arcadible.com/build/initialization">Initialization</a> ·
|
|
16
16
|
<a href="https://developer.arcadible.com/build/saves">Saves</a> ·
|
|
17
|
+
<a href="https://developer.arcadible.com/build/pausing">Pausing</a> ·
|
|
17
18
|
<a href="https://arcadible.com">Arcadible</a> ·
|
|
18
19
|
<a href="https://studio.arcadible.com">Studio</a>
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<br>
|
|
22
23
|
|
|
23
|
-
The Arcadible SDK is optional: a game runs on [Arcadible](https://arcadible.com) without it. With it, a game gets
|
|
24
|
+
The Arcadible SDK is optional: a game runs on [Arcadible](https://arcadible.com) without it. With it, a game gets these from the play page around it:
|
|
25
|
+
|
|
26
|
+
- **[Initialization](https://developer.arcadible.com/build/initialization),** bracketed, so Arcadible's loading screen shows the game's splash and progress until its first frame is drawn.
|
|
27
|
+
- **[Saves](https://developer.arcadible.com/build/saves),** kept by the play page, so the player's progress carries over from one version to the next.
|
|
28
|
+
- **[Pausing](https://developer.arcadible.com/build/pausing):** Arcadible tells the game when to stop, go quiet, and save, and when to carry on.
|
|
29
|
+
- **[`system` and `context`](https://developer.arcadible.com/references/arcadible-sdk#system):** the store's language, and when this run launched.
|
|
24
30
|
|
|
25
31
|
## Install
|
|
26
32
|
|
|
@@ -58,11 +64,28 @@ Saves are for what your game remembers between sessions, such as progress, setti
|
|
|
58
64
|
|
|
59
65
|
Off Arcadible, such as on your dev server, the package still works, with the same rules and limits: there's no loading screen, so `initComplete()` resolves at once, saves are kept in the game's own browser storage, `arcadible.saves.where` is `"build"`, and `arcadible.build` is `null`.
|
|
60
66
|
|
|
67
|
+
## Pause and resume
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
arcadible.on("pause", async () => {
|
|
71
|
+
loop.stop();
|
|
72
|
+
audio.mute();
|
|
73
|
+
await arcadible.saves.write("progress", JSON.stringify(progress));
|
|
74
|
+
});
|
|
75
|
+
arcadible.on("resume", () => {
|
|
76
|
+
loop.start();
|
|
77
|
+
audio.unmute();
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Arcadible pauses your game when the page is hidden, when it covers your game, and before it closes or reloads it, when it waits a moment for your save. Stop, go quiet, and save, drawing nothing over your game; on `resume`, carry on, or show a "Ready?" first in an action game.
|
|
82
|
+
|
|
61
83
|
## Learn more
|
|
62
84
|
|
|
63
85
|
- [Initialization](https://developer.arcadible.com/build/initialization): where each call goes, progress, and what comes after.
|
|
64
86
|
- [Saves](https://developer.arcadible.com/build/saves): using saves well, across versions, channels, and tabs, and where they're kept.
|
|
65
|
-
- [
|
|
87
|
+
- [Pausing](https://developer.arcadible.com/build/pausing): what a pause asks of your game, and resuming.
|
|
88
|
+
- [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk): including it, every call, the saves' included, `system` and `context`, and every code.
|
|
66
89
|
- [Limits](https://developer.arcadible.com/references/limits#saves): how many saves, and how large.
|
|
67
90
|
|
|
68
91
|
## License
|
package/dist/host.d.ts
CHANGED
package/dist/host.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// The page's side of the bus. The play page uses `createHost`;
|
|
2
2
|
// a game standing alone runs `serve` in its own page.
|
|
3
|
+
// [import]
|
|
4
|
+
import { log } from "./bus/protocol.js";
|
|
3
5
|
import { answerHellos, createServer } from "./bus/server.js";
|
|
4
6
|
import { contextServer } from "./context/server.js";
|
|
5
7
|
import { isTime } from "./context/rules.js";
|
|
6
8
|
import { createHolds } from "./lifecycle/holds.js";
|
|
7
|
-
import {
|
|
9
|
+
import { browserLanguage } from "./system/rules.js";
|
|
8
10
|
import { createSystem } from "./system/server.js";
|
|
9
11
|
import { lifecycleServer } from "./lifecycle/server.js";
|
|
10
12
|
import { savesServer } from "./saves/server.js";
|
|
@@ -32,6 +34,18 @@ export function serve(options) {
|
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
// [function]
|
|
37
|
+
// The launch's time: the page's, by Arcadible's clock, or the device's when
|
|
38
|
+
// none is given (as under `arc serve`). One that isn't an ISO time in UTC is
|
|
39
|
+
// a bug in the page, said in the console, and the device's time is used, so
|
|
40
|
+
// the game still gets one.
|
|
41
|
+
function launchedAt(given) {
|
|
42
|
+
if (isTime(given))
|
|
43
|
+
return given;
|
|
44
|
+
if (given !== undefined) {
|
|
45
|
+
log("error", `launchedAt isn't an ISO time in UTC: ${JSON.stringify(given)}; the device's time is used.`);
|
|
46
|
+
}
|
|
47
|
+
return new Date().toISOString();
|
|
48
|
+
}
|
|
35
49
|
// The play page's host: it answers its iframe's hello with a welcome and a
|
|
36
50
|
// private channel. Call it before inserting the iframe, so no hello is
|
|
37
51
|
// missed. It holds the game while the page is hidden; `hold` holds it for
|
|
@@ -41,7 +55,7 @@ export function createHost(options) {
|
|
|
41
55
|
const win = options.window ?? window;
|
|
42
56
|
const holds = createHolds();
|
|
43
57
|
const system = createSystem({
|
|
44
|
-
language: options.language ??
|
|
58
|
+
language: options.language ?? browserLanguage(win.navigator?.language),
|
|
45
59
|
});
|
|
46
60
|
const answering = answerHellos({
|
|
47
61
|
frame: options.frame,
|
|
@@ -53,11 +67,7 @@ export function createHost(options) {
|
|
|
53
67
|
context: {
|
|
54
68
|
// Arcadible's time when given, else the device's: never a time the
|
|
55
69
|
// game would refuse.
|
|
56
|
-
launch: {
|
|
57
|
-
at: isTime(options.launchedAt)
|
|
58
|
-
? options.launchedAt
|
|
59
|
-
: new Date().toISOString(),
|
|
60
|
-
},
|
|
70
|
+
launch: { at: launchedAt(options.launchedAt) },
|
|
61
71
|
},
|
|
62
72
|
}),
|
|
63
73
|
window: win,
|
package/dist/system/client.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Bus } from "../bus/client.js";
|
|
2
2
|
/** Arcadible's state for the player, which changes while the game runs. */
|
|
3
3
|
export interface System {
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The language Arcadible's store shows this player: a well-formed BCP 47
|
|
6
|
+
* tag in its canonical form ("en", "ja", later perhaps "zh-Hant" or
|
|
7
|
+
* "pt-BR"), ready for `Intl`. Match it against your game's languages by
|
|
8
|
+
* dropping parts from the end ("zh-Hant-TW", "zh-Hant", "zh").
|
|
9
|
+
*/
|
|
5
10
|
readonly language: string;
|
|
6
11
|
/**
|
|
7
12
|
* Called when something changes, with the new state (this same object)
|
package/dist/system/rules.d.ts
CHANGED
|
@@ -4,11 +4,19 @@ export declare const Names: {
|
|
|
4
4
|
export type SystemState = {
|
|
5
5
|
language: string;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* A language tag (BCP 47, as the web, iOS, and Android use) in its canonical
|
|
9
|
+
* form, `zh-hant-tw` as `zh-Hant-TW`, or null if it isn't a well-formed one
|
|
10
|
+
* (`en_US`, `English`, empty). Only its shape is checked, never whether
|
|
11
|
+
* Arcadible uses it, so a language added later is never refused.
|
|
12
|
+
*/
|
|
13
|
+
export declare function languageOf(tag: unknown): string | null;
|
|
14
|
+
/** Whether a value is a language tag in its canonical form. */
|
|
8
15
|
export declare function isLanguage(value: unknown): value is string;
|
|
9
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The state as the game keeps it: only fields it knows, each checked, the
|
|
18
|
+
* language in its canonical form.
|
|
19
|
+
*/
|
|
10
20
|
export declare function stateOf(value: unknown): SystemState | null;
|
|
11
|
-
/** A language
|
|
12
|
-
export declare function
|
|
13
|
-
/** A browser's language as a short tag: `en-US` as `en`, or `en`. */
|
|
14
|
-
export declare function shortLanguage(tag: string | undefined): string;
|
|
21
|
+
/** A browser's language (`navigator.language`) as the page sends it, or `en`. */
|
|
22
|
+
export declare function browserLanguage(tag: string | undefined): string;
|
package/dist/system/rules.js
CHANGED
|
@@ -8,30 +8,54 @@ export const Names = {
|
|
|
8
8
|
// every change.
|
|
9
9
|
State: "system.state",
|
|
10
10
|
};
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// The longest language tag kept: room for a language, script, region, and a
|
|
12
|
+
// variant or two (`zh-Hant-TW`, `sl-rozaj-biske`), not a paragraph.
|
|
13
|
+
const MAX_LANGUAGE = 35;
|
|
14
|
+
// A tag's shape, where the browser can't say (no `Intl.getCanonicalLocales`).
|
|
15
|
+
const SHAPE = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/u;
|
|
16
|
+
// The language itself, first: an ISO 639 code, two or three letters. BCP 47
|
|
17
|
+
// also allows five to eight (`english` is well-formed), which nothing uses.
|
|
18
|
+
const PRIMARY = /^[A-Za-z]{2,3}(-|$)/u;
|
|
13
19
|
// [function]
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
/**
|
|
21
|
+
* A language tag (BCP 47, as the web, iOS, and Android use) in its canonical
|
|
22
|
+
* form, `zh-hant-tw` as `zh-Hant-TW`, or null if it isn't a well-formed one
|
|
23
|
+
* (`en_US`, `English`, empty). Only its shape is checked, never whether
|
|
24
|
+
* Arcadible uses it, so a language added later is never refused.
|
|
25
|
+
*/
|
|
26
|
+
export function languageOf(tag) {
|
|
27
|
+
if (typeof tag !== "string" || tag.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
if (tag.length > MAX_LANGUAGE || !PRIMARY.test(tag))
|
|
30
|
+
return null;
|
|
31
|
+
if (typeof Intl === "undefined" || !Intl.getCanonicalLocales) {
|
|
32
|
+
return SHAPE.test(tag) ? tag : null;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return Intl.getCanonicalLocales(tag)[0] ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
17
40
|
}
|
|
18
41
|
// [function]
|
|
19
|
-
/**
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
return null;
|
|
23
|
-
return { language: value["language"] };
|
|
42
|
+
/** Whether a value is a language tag in its canonical form. */
|
|
43
|
+
export function isLanguage(value) {
|
|
44
|
+
return typeof value === "string" && languageOf(value) === value;
|
|
24
45
|
}
|
|
25
46
|
// [function]
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The state as the game keeps it: only fields it knows, each checked, the
|
|
49
|
+
* language in its canonical form.
|
|
50
|
+
*/
|
|
51
|
+
export function stateOf(value) {
|
|
52
|
+
if (!isObject(value))
|
|
29
53
|
return null;
|
|
30
|
-
const
|
|
31
|
-
return
|
|
54
|
+
const language = languageOf(value["language"]);
|
|
55
|
+
return language ? { language } : null;
|
|
32
56
|
}
|
|
33
57
|
// [function]
|
|
34
|
-
/** A browser's language as
|
|
35
|
-
export function
|
|
58
|
+
/** A browser's language (`navigator.language`) as the page sends it, or `en`. */
|
|
59
|
+
export function browserLanguage(tag) {
|
|
36
60
|
return languageOf(tag) ?? "en";
|
|
37
61
|
}
|
package/dist/system/server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// The page's side of `system`: Arcadible's state for the player, sent as
|
|
2
2
|
// the game connects and again whenever it changes. Nothing waits on it.
|
|
3
|
-
import { languageOf, Names,
|
|
3
|
+
import { browserLanguage, languageOf, Names, } from "./rules.js";
|
|
4
4
|
// [function]
|
|
5
5
|
export function createSystem(initial) {
|
|
6
|
-
// only a
|
|
7
|
-
let state = { ...initial, language:
|
|
6
|
+
// only a well-formed tag goes to the game, in its canonical form.
|
|
7
|
+
let state = { ...initial, language: browserLanguage(initial.language) };
|
|
8
8
|
let context = null;
|
|
9
9
|
return {
|
|
10
10
|
set(next) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.5.0
|
|
1
|
+
export declare const VERSION = "1.5.0";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Offst, Inc.",
|
|
3
|
-
"description": "The Arcadible SDK:
|
|
3
|
+
"description": "The Arcadible SDK: how your game works with Arcadible.",
|
|
4
4
|
"devDependencies": {
|
|
5
5
|
"@playwright/test": "1.63.0",
|
|
6
6
|
"@types/node": "^25.0.3",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
],
|
|
44
44
|
"type": "module",
|
|
45
45
|
"types": "./dist/index.d.ts",
|
|
46
|
-
"version": "1.5.0
|
|
46
|
+
"version": "1.5.0"
|
|
47
47
|
}
|