@proveanything/smartlinks 2.0.54 → 2.0.56
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/api/index.d.ts +0 -1
- package/dist/api/index.js +0 -1
- package/dist/api/realtime.d.ts +31 -88
- package/dist/api/realtime.js +239 -93
- package/dist/docs/API_SUMMARY.md +66 -29
- package/dist/docs/overview.md +2 -3
- package/dist/docs/realtime.md +118 -765
- package/dist/http.js +3 -0
- package/dist/openapi.yaml +105 -27
- package/dist/types/appManifest.d.ts +11 -0
- package/dist/types/realtime.d.ts +44 -0
- package/docs/API_SUMMARY.md +66 -29
- package/docs/overview.md +2 -3
- package/docs/realtime.md +118 -765
- package/openapi.yaml +105 -27
- package/package.json +1 -1
- package/scripts/doctor.mjs +25 -0
- package/dist/api/sequence.d.ts +0 -38
- package/dist/api/sequence.js +0 -29
- package/dist/docs/sequences.md +0 -125
- package/docs/sequences.md +0 -125
package/dist/http.js
CHANGED
|
@@ -210,6 +210,9 @@ function shouldSkipCache(path) {
|
|
|
210
210
|
// Never cache auth, account, or user-identity endpoints — they depend on session state.
|
|
211
211
|
if (/\/(auth|account)\//i.test(path))
|
|
212
212
|
return true;
|
|
213
|
+
// Realtime tokens are single-use (a reused one is rejected by the provider) — never cache them.
|
|
214
|
+
if (/\/(push\/token|realtime\/)/i.test(path))
|
|
215
|
+
return true;
|
|
213
216
|
// Never cache /me endpoints (contact/me, loyalty/me, etc.) — always user-specific.
|
|
214
217
|
if (/\/me(\?|$)/i.test(path))
|
|
215
218
|
return true;
|
package/dist/openapi.yaml
CHANGED
|
@@ -123,7 +123,7 @@ paths:
|
|
|
123
123
|
get:
|
|
124
124
|
tags:
|
|
125
125
|
- realtime
|
|
126
|
-
summary:
|
|
126
|
+
summary: (subscribe only).
|
|
127
127
|
operationId: realtime_getAdminToken
|
|
128
128
|
security:
|
|
129
129
|
- bearerAuth: []
|
|
@@ -13960,7 +13960,7 @@ paths:
|
|
|
13960
13960
|
get:
|
|
13961
13961
|
tags:
|
|
13962
13962
|
- realtime
|
|
13963
|
-
summary:
|
|
13963
|
+
summary: "`collection:{collectionId}:*` (subscribe; publish only for the collection's admins)."
|
|
13964
13964
|
operationId: realtime_getPublicToken
|
|
13965
13965
|
security: []
|
|
13966
13966
|
parameters:
|
|
@@ -14018,6 +14018,33 @@ paths:
|
|
|
14018
14018
|
description: Unauthorized
|
|
14019
14019
|
404:
|
|
14020
14020
|
description: Not found
|
|
14021
|
+
/public/realtime/publish:
|
|
14022
|
+
post:
|
|
14023
|
+
tags:
|
|
14024
|
+
- realtime
|
|
14025
|
+
summary: "One of your app's declared channels (manifest `realtime.channels`)."
|
|
14026
|
+
operationId: realtime_channel
|
|
14027
|
+
security: []
|
|
14028
|
+
responses:
|
|
14029
|
+
200:
|
|
14030
|
+
description: Success
|
|
14031
|
+
content:
|
|
14032
|
+
application/json:
|
|
14033
|
+
schema:
|
|
14034
|
+
type: object
|
|
14035
|
+
additionalProperties: true
|
|
14036
|
+
400:
|
|
14037
|
+
description: Bad request
|
|
14038
|
+
401:
|
|
14039
|
+
description: Unauthorized
|
|
14040
|
+
404:
|
|
14041
|
+
description: Not found
|
|
14042
|
+
requestBody:
|
|
14043
|
+
required: true
|
|
14044
|
+
content:
|
|
14045
|
+
application/json:
|
|
14046
|
+
schema:
|
|
14047
|
+
$ref: "#/components/schemas/RealtimeScope"
|
|
14021
14048
|
/public/tags/{tagId}:
|
|
14022
14049
|
get:
|
|
14023
14050
|
tags:
|
|
@@ -19618,6 +19645,9 @@ components:
|
|
|
19618
19645
|
$ref: "#/components/schemas/AppManifestExecutor"
|
|
19619
19646
|
functions:
|
|
19620
19647
|
$ref: "#/components/schemas/AppManifestFunctions"
|
|
19648
|
+
realtime:
|
|
19649
|
+
type: object
|
|
19650
|
+
additionalProperties: true
|
|
19621
19651
|
data:
|
|
19622
19652
|
$ref: "#/components/schemas/AppDataDeclaration"
|
|
19623
19653
|
headless:
|
|
@@ -29830,6 +29860,79 @@ components:
|
|
|
29830
29860
|
- nonce
|
|
29831
29861
|
- mac
|
|
29832
29862
|
- clientId
|
|
29863
|
+
RealtimeScope:
|
|
29864
|
+
type: object
|
|
29865
|
+
properties:
|
|
29866
|
+
collectionId:
|
|
29867
|
+
type: string
|
|
29868
|
+
appId:
|
|
29869
|
+
type: string
|
|
29870
|
+
required:
|
|
29871
|
+
- collectionId
|
|
29872
|
+
RealtimeMessage:
|
|
29873
|
+
type: object
|
|
29874
|
+
properties:
|
|
29875
|
+
channel:
|
|
29876
|
+
type: string
|
|
29877
|
+
event:
|
|
29878
|
+
type: string
|
|
29879
|
+
data: {}
|
|
29880
|
+
at:
|
|
29881
|
+
type: number
|
|
29882
|
+
required:
|
|
29883
|
+
- channel
|
|
29884
|
+
- event
|
|
29885
|
+
- data
|
|
29886
|
+
RealtimeChangeEvent:
|
|
29887
|
+
type: object
|
|
29888
|
+
properties:
|
|
29889
|
+
type:
|
|
29890
|
+
type: string
|
|
29891
|
+
id:
|
|
29892
|
+
type: string
|
|
29893
|
+
appId:
|
|
29894
|
+
type: string
|
|
29895
|
+
recordType:
|
|
29896
|
+
type: string
|
|
29897
|
+
productId:
|
|
29898
|
+
type: string
|
|
29899
|
+
proofId:
|
|
29900
|
+
type: string
|
|
29901
|
+
status:
|
|
29902
|
+
type: string
|
|
29903
|
+
at:
|
|
29904
|
+
type: number
|
|
29905
|
+
required:
|
|
29906
|
+
- type
|
|
29907
|
+
- at
|
|
29908
|
+
RealtimeChangeFilter:
|
|
29909
|
+
type: object
|
|
29910
|
+
properties:
|
|
29911
|
+
type:
|
|
29912
|
+
type: string
|
|
29913
|
+
id:
|
|
29914
|
+
type: string
|
|
29915
|
+
appId:
|
|
29916
|
+
type: string
|
|
29917
|
+
recordType:
|
|
29918
|
+
type: string
|
|
29919
|
+
productId:
|
|
29920
|
+
type: string
|
|
29921
|
+
proofId:
|
|
29922
|
+
type: string
|
|
29923
|
+
RealtimeChannelDeclaration:
|
|
29924
|
+
type: object
|
|
29925
|
+
properties:
|
|
29926
|
+
name:
|
|
29927
|
+
type: string
|
|
29928
|
+
subscribe:
|
|
29929
|
+
type: object
|
|
29930
|
+
additionalProperties: true
|
|
29931
|
+
publish:
|
|
29932
|
+
type: object
|
|
29933
|
+
additionalProperties: true
|
|
29934
|
+
required:
|
|
29935
|
+
- name
|
|
29833
29936
|
RealtimeChannelPattern:
|
|
29834
29937
|
type: string
|
|
29835
29938
|
ResearchFetchRequest:
|
|
@@ -30958,28 +31061,3 @@ components:
|
|
|
30958
31061
|
properties:
|
|
30959
31062
|
appId:
|
|
30960
31063
|
type: string
|
|
30961
|
-
AllocateSequenceInput:
|
|
30962
|
-
type: object
|
|
30963
|
-
properties:
|
|
30964
|
-
appId:
|
|
30965
|
-
type: string
|
|
30966
|
-
sequenceId:
|
|
30967
|
-
type: string
|
|
30968
|
-
subjectId:
|
|
30969
|
-
type: string
|
|
30970
|
-
productId:
|
|
30971
|
-
type: string
|
|
30972
|
-
required:
|
|
30973
|
-
- appId
|
|
30974
|
-
- sequenceId
|
|
30975
|
-
- subjectId
|
|
30976
|
-
AllocatedSequence:
|
|
30977
|
-
type: object
|
|
30978
|
-
properties:
|
|
30979
|
-
number:
|
|
30980
|
-
type: number
|
|
30981
|
-
isNew:
|
|
30982
|
-
type: boolean
|
|
30983
|
-
required:
|
|
30984
|
-
- number
|
|
30985
|
-
- isNew
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import type { AppDataDeclaration, AppHeadlessDeclaration } from './headless.js';
|
|
4
|
+
import type { RealtimeChannelDeclaration } from './realtime.js';
|
|
4
5
|
/**
|
|
5
6
|
* A bundle (widget or container) as returned by the collection widgets endpoint.
|
|
6
7
|
*
|
|
@@ -806,6 +807,16 @@ export interface AppManifest {
|
|
|
806
807
|
* @see AppManifestFunctions
|
|
807
808
|
*/
|
|
808
809
|
functions?: AppManifestFunctions;
|
|
810
|
+
/**
|
|
811
|
+
* SmartLinks Realtime: the live channels this app uses, with who may listen and who may send
|
|
812
|
+
* (docs/realtime.md). Only declared channels exist — anything else is refused.
|
|
813
|
+
* ```json
|
|
814
|
+
* "realtime": { "channels": [ { "name": "draw", "subscribe": "public", "publish": "admin" } ] }
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
817
|
+
realtime?: {
|
|
818
|
+
channels: RealtimeChannelDeclaration[];
|
|
819
|
+
};
|
|
809
820
|
/**
|
|
810
821
|
* The app's public data model: its data types, how each is stored, fields, examples and read
|
|
811
822
|
* recipes. Required for `headless`. @see AppDataDeclaration · docs/headless-providers.md
|
package/dist/types/realtime.d.ts
CHANGED
|
@@ -42,3 +42,47 @@ export interface AblyTokenRequest {
|
|
|
42
42
|
* - collection:abc:users:active
|
|
43
43
|
*/
|
|
44
44
|
export type RealtimeChannelPattern = string;
|
|
45
|
+
/** Where a realtime listener lives: the collection, and (for app channels) the app. */
|
|
46
|
+
export interface RealtimeScope {
|
|
47
|
+
collectionId: string;
|
|
48
|
+
appId?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A message on a channel. `channel` is the logical name ("draw", "feed:admin"). */
|
|
51
|
+
export interface RealtimeMessage {
|
|
52
|
+
channel: string;
|
|
53
|
+
event: string;
|
|
54
|
+
data: unknown;
|
|
55
|
+
/** When it was sent (ms since epoch), when known. */
|
|
56
|
+
at?: number;
|
|
57
|
+
}
|
|
58
|
+
/** "This changed" — ids only; refetch through the normal API. */
|
|
59
|
+
export interface RealtimeChangeEvent {
|
|
60
|
+
/** 'product' | 'proof' | 'asset' | 'record' | 'collection' | 'collection.apps' | 'settings' | … */
|
|
61
|
+
type: string;
|
|
62
|
+
id?: string;
|
|
63
|
+
appId?: string;
|
|
64
|
+
recordType?: string;
|
|
65
|
+
productId?: string;
|
|
66
|
+
proofId?: string;
|
|
67
|
+
/** 'created' | 'updated' | 'deleted' (records) or 'create' | 'update' | 'delete' (products, proofs…) */
|
|
68
|
+
status?: string;
|
|
69
|
+
at: number;
|
|
70
|
+
}
|
|
71
|
+
/** Which changes to hear about — every field given must match. */
|
|
72
|
+
export interface RealtimeChangeFilter {
|
|
73
|
+
type?: string;
|
|
74
|
+
id?: string;
|
|
75
|
+
appId?: string;
|
|
76
|
+
recordType?: string;
|
|
77
|
+
productId?: string;
|
|
78
|
+
proofId?: string;
|
|
79
|
+
}
|
|
80
|
+
/** A channel an app declares in its manifest (`realtime.channels`). */
|
|
81
|
+
export interface RealtimeChannelDeclaration {
|
|
82
|
+
/** Lowercase letters, numbers, - and _; up to 40 characters. */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Who may listen. Default 'admin'. */
|
|
85
|
+
subscribe?: "public" | "signedIn" | "admin";
|
|
86
|
+
/** Who may send: those levels, or 'server' (only the app's server functions). Default 'server'. */
|
|
87
|
+
publish?: "public" | "signedIn" | "admin" | "server";
|
|
88
|
+
}
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.56 | Generated: 2026-10-12T20:58:59.335Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -154,7 +154,6 @@ The Smartlinks SDK is organized into the following namespaces:
|
|
|
154
154
|
- **realtime** - Functions for realtime operations
|
|
155
155
|
- **research** - Functions for research operations
|
|
156
156
|
- **secrets** - Functions for secrets operations
|
|
157
|
-
- **sequence** - Functions for sequence operations
|
|
158
157
|
- **tags** - Functions for tags operations
|
|
159
158
|
- **template** - Functions for template operations
|
|
160
159
|
- **translations** - Functions for translations operations
|
|
@@ -2851,6 +2850,7 @@ interface AppManifest {
|
|
|
2851
2850
|
publicViews?: PublicView[];
|
|
2852
2851
|
executor?: AppManifestExecutor;
|
|
2853
2852
|
functions?: AppManifestFunctions;
|
|
2853
|
+
realtime?: { channels: RealtimeChannelDeclaration[] };
|
|
2854
2854
|
data?: AppDataDeclaration;
|
|
2855
2855
|
headless?: AppHeadlessDeclaration;
|
|
2856
2856
|
[key: string]: any;
|
|
@@ -9142,6 +9142,59 @@ interface AblyTokenRequest {
|
|
|
9142
9142
|
}
|
|
9143
9143
|
```
|
|
9144
9144
|
|
|
9145
|
+
**RealtimeScope** (interface)
|
|
9146
|
+
```typescript
|
|
9147
|
+
interface RealtimeScope {
|
|
9148
|
+
collectionId: string
|
|
9149
|
+
appId?: string
|
|
9150
|
+
}
|
|
9151
|
+
```
|
|
9152
|
+
|
|
9153
|
+
**RealtimeMessage** (interface)
|
|
9154
|
+
```typescript
|
|
9155
|
+
interface RealtimeMessage {
|
|
9156
|
+
channel: string
|
|
9157
|
+
event: string
|
|
9158
|
+
data: unknown
|
|
9159
|
+
at?: number
|
|
9160
|
+
}
|
|
9161
|
+
```
|
|
9162
|
+
|
|
9163
|
+
**RealtimeChangeEvent** (interface)
|
|
9164
|
+
```typescript
|
|
9165
|
+
interface RealtimeChangeEvent {
|
|
9166
|
+
type: string
|
|
9167
|
+
id?: string
|
|
9168
|
+
appId?: string
|
|
9169
|
+
recordType?: string
|
|
9170
|
+
productId?: string
|
|
9171
|
+
proofId?: string
|
|
9172
|
+
status?: string
|
|
9173
|
+
at: number
|
|
9174
|
+
}
|
|
9175
|
+
```
|
|
9176
|
+
|
|
9177
|
+
**RealtimeChangeFilter** (interface)
|
|
9178
|
+
```typescript
|
|
9179
|
+
interface RealtimeChangeFilter {
|
|
9180
|
+
type?: string
|
|
9181
|
+
id?: string
|
|
9182
|
+
appId?: string
|
|
9183
|
+
recordType?: string
|
|
9184
|
+
productId?: string
|
|
9185
|
+
proofId?: string
|
|
9186
|
+
}
|
|
9187
|
+
```
|
|
9188
|
+
|
|
9189
|
+
**RealtimeChannelDeclaration** (interface)
|
|
9190
|
+
```typescript
|
|
9191
|
+
interface RealtimeChannelDeclaration {
|
|
9192
|
+
name: string
|
|
9193
|
+
subscribe?: "public" | "signedIn" | "admin"
|
|
9194
|
+
publish?: "public" | "signedIn" | "admin" | "server"
|
|
9195
|
+
}
|
|
9196
|
+
```
|
|
9197
|
+
|
|
9145
9198
|
**RealtimeChannelPattern** = `string`
|
|
9146
9199
|
|
|
9147
9200
|
### research
|
|
@@ -9869,26 +9922,6 @@ interface AppSecretOptions {
|
|
|
9869
9922
|
}
|
|
9870
9923
|
```
|
|
9871
9924
|
|
|
9872
|
-
### sequence (api)
|
|
9873
|
-
|
|
9874
|
-
**AllocateSequenceInput** (interface)
|
|
9875
|
-
```typescript
|
|
9876
|
-
interface AllocateSequenceInput {
|
|
9877
|
-
appId: string
|
|
9878
|
-
sequenceId: string
|
|
9879
|
-
subjectId: string
|
|
9880
|
-
productId?: string
|
|
9881
|
-
}
|
|
9882
|
-
```
|
|
9883
|
-
|
|
9884
|
-
**AllocatedSequence** (interface)
|
|
9885
|
-
```typescript
|
|
9886
|
-
interface AllocatedSequence {
|
|
9887
|
-
number: number
|
|
9888
|
-
isNew: boolean
|
|
9889
|
-
}
|
|
9890
|
-
```
|
|
9891
|
-
|
|
9892
9925
|
### conditions (utils)
|
|
9893
9926
|
|
|
9894
9927
|
**BaseCondition** (interface)
|
|
@@ -12189,11 +12222,20 @@ Reset rate limit for a user
|
|
|
12189
12222
|
|
|
12190
12223
|
### realtime
|
|
12191
12224
|
|
|
12225
|
+
**channel**(scope: RealtimeScope & { appId: string }, name: string) → `void`
|
|
12226
|
+
One of your app's declared channels (manifest `realtime.channels`). ```ts const draw = realtime.channel({ collectionId, appId }, "draw") const off = draw.subscribe("status", (m) => setStatus(m.data)) await draw.publish("status", { phase: "spinning" }) // if the channel lets this viewer publish ```
|
|
12227
|
+
|
|
12228
|
+
**onChange**(scope: RealtimeScope, filter: RealtimeChangeFilter, onChangeFn: (change: RealtimeChangeEvent) → `void`
|
|
12229
|
+
Something changed: refetch it. IDs only — never data. Admins of the collection see every change; everyone else sees changes to public things (public app records, products). Filter by `type` ('product' | 'proof' | 'asset' | 'record' | 'collection' | 'collection.apps' | 'settings'), `id`, `appId`, `recordType`, `productId`, `proofId`. ```ts const off = realtime.onChange({ collectionId }, { type: "record", appId, recordType: "entry" }, () => refetch()) ```
|
|
12230
|
+
|
|
12231
|
+
**onMine**(scope: RealtimeScope, onMessage: (msg: RealtimeMessage) → `void`
|
|
12232
|
+
Messages for the signed-in person only (e.g. "scan" — a tag they just scanned on another device).
|
|
12233
|
+
|
|
12192
12234
|
**getPublicToken**(params: RealtimeTokenRequest) → `Promise<AblyTokenRequest>`
|
|
12193
|
-
|
|
12235
|
+
`collection:{collectionId}:*` (subscribe; publish only for the collection's admins).
|
|
12194
12236
|
|
|
12195
12237
|
**getAdminToken**() → `Promise<AblyTokenRequest>`
|
|
12196
|
-
|
|
12238
|
+
(subscribe only).
|
|
12197
12239
|
|
|
12198
12240
|
### research
|
|
12199
12241
|
|
|
@@ -12251,11 +12293,6 @@ Soft-delete a secret. DELETE /secrets/:ref
|
|
|
12251
12293
|
id: string,
|
|
12252
12294
|
query: { limit?: number; offset?: number } = {}) → `Promise<SegmentRecipientsResponse>`
|
|
12253
12295
|
|
|
12254
|
-
### sequence
|
|
12255
|
-
|
|
12256
|
-
**allocate**(collectionId: string, input: AllocateSequenceInput) → `Promise<AllocatedSequence>`
|
|
12257
|
-
Allocate (or return the existing) sequence number for a subject. Idempotent — safe to call on load (auto-enter) and on a button tap; a subject that already has a number gets it back with `isNew: false`. const { number, isNew } = await sequence.allocate(collectionId, { appId: 'raffle-app', sequenceId: 'raffle', subjectId: claimSetId, }) `ctx.sl.counters.next()` + `ctx.sl.appRecords.claim()` (docs/server-functions.md).
|
|
12258
|
-
|
|
12259
12296
|
### sessions
|
|
12260
12297
|
|
|
12261
12298
|
**stats**(collectionId: string) → `Promise<SessionStatistics>`
|
package/docs/overview.md
CHANGED
|
@@ -69,7 +69,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
69
69
|
| **Portal Request Action** | `docs/portal-request-action.md` | Triggering portal built-in actions (__qrScanner, __share, __logout, etc.) from sub-apps |
|
|
70
70
|
| **Interactions** | `docs/interactions.md` | Business events, outcomes, voting, competitions, and journey triggers |
|
|
71
71
|
| **App Config Files** | `docs/app-manifest.md` | Full field-by-field reference for both JSON config files |
|
|
72
|
-
| **
|
|
72
|
+
| **Realtime** | `docs/realtime.md` | Live updates: change events (refetch when something changes) and declared app channels (a draw, a status) — no provider library or keys |
|
|
73
73
|
| **Liquid Templates** | `docs/liquid-templates.md` | Dynamic content rendering with LiquidJS |
|
|
74
74
|
| **Iframe Responder** | `docs/iframe-responder.md` | Iframe communication and proxy mode internals |
|
|
75
75
|
| **AI Guide Template** | `docs/ai-guide-template.md` | Template for creating `public/ai-guide.md` — customise per app |
|
|
@@ -93,7 +93,6 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
93
93
|
| **Container Tracking** | `docs/container-tracking.md` | Physical/logical **item** container groupings — *not* app containers (see containers.md) |
|
|
94
94
|
| **Lots** | `docs/lots.md` | Cross-SKU production "Lot" entity |
|
|
95
95
|
| **Loyalty** | `docs/loyalty.md` | Points, members, earning rules |
|
|
96
|
-
| **Sequences** | `docs/sequences.md` | RETIRING — number things in your own server function: `ctx.sl.counters` + `appRecords.claim` (server-functions.md) |
|
|
97
96
|
| **Integrations** | `docs/integrations.md` | Inbound/outbound external-system integration flows |
|
|
98
97
|
| **Item Context** | `docs/item-context.md` | The `itemContext` container prop (serial / NFC authenticity context) |
|
|
99
98
|
| **Native Facade** | `docs/native-facade.md` | `host.native` / `SL.native` device-capability facade |
|
|
@@ -359,7 +358,7 @@ The SmartLinks SDK is organised into namespaces. See `docs/API_SUMMARY.md` for t
|
|
|
359
358
|
- `contact` — Customer contact management
|
|
360
359
|
- `comms` / `broadcasts` — Notifications and campaigns
|
|
361
360
|
- `asset` — File/image uploads
|
|
362
|
-
- `realtime` —
|
|
361
|
+
- `realtime` — SmartLinks Realtime: `onChange`, `channel`, `onMine` (see `docs/realtime.md`)
|
|
363
362
|
|
|
364
363
|
---
|
|
365
364
|
|