@flow-industries/id 0.24.1 → 0.24.2
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/contracts/v1/sdk-exports.json +30 -0
- package/dist/sdk/client/index.d.ts +1 -1
- package/dist/sdk/client/profile-button.js +4 -1
- package/dist/sdk/db/schema.d.ts +160 -0
- package/dist/sdk/db/schema.js +77 -0
- package/dist/sdk/settings/cosmetics.d.ts +1 -0
- package/dist/sdk/settings/cosmetics.js +10 -0
- package/dist/sdk/settings/ear-items.json +37 -0
- package/dist/sdk/settings/surfaces.js +15 -4
- package/dist/sdk/types/account-data.d.ts +17 -0
- package/dist/sdk/types/auth.d.ts +18 -0
- package/dist/sdk/types/cosmetics.d.ts +1 -1
- package/dist/sdk/types/events.d.ts +5 -2
- package/dist/sdk/types/index.d.ts +3 -2
- package/dist/sdk/types/notifications.d.ts +65 -0
- package/dist/sdk/types/notifications.js +9 -0
- package/package.json +1 -1
|
@@ -347,6 +347,11 @@
|
|
|
347
347
|
"type": true,
|
|
348
348
|
"value": false
|
|
349
349
|
},
|
|
350
|
+
{
|
|
351
|
+
"name": "MarkNotificationsReadInput",
|
|
352
|
+
"type": true,
|
|
353
|
+
"value": false
|
|
354
|
+
},
|
|
350
355
|
{
|
|
351
356
|
"name": "matchScore",
|
|
352
357
|
"type": false,
|
|
@@ -382,6 +387,31 @@
|
|
|
382
387
|
"type": false,
|
|
383
388
|
"value": true
|
|
384
389
|
},
|
|
390
|
+
{
|
|
391
|
+
"name": "NotificationCounts",
|
|
392
|
+
"type": true,
|
|
393
|
+
"value": false
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"name": "NotificationEntry",
|
|
397
|
+
"type": true,
|
|
398
|
+
"value": false
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"name": "NotificationPage",
|
|
402
|
+
"type": true,
|
|
403
|
+
"value": false
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"name": "NotificationReadResult",
|
|
407
|
+
"type": true,
|
|
408
|
+
"value": false
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"name": "NotificationRefusal",
|
|
412
|
+
"type": true,
|
|
413
|
+
"value": false
|
|
414
|
+
},
|
|
385
415
|
{
|
|
386
416
|
"name": "openProfile",
|
|
387
417
|
"type": false,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { defaultIdHost, isLocalHostname } from "../id-host";
|
|
2
|
-
export type { AccessKeyOptions, ActionCatalogEntry, ActionCatalogResponse, ActionCurve, ActionEventRequest, ActionEventResponse, ActionSubjectRef, ActionSurface, ActionsApi, ActionTransitionKind, ActiveAction, ActiveActionResponse, ActiveSessionConflict, AdditionalSession, Address, CatalogFieldRef, ClockAnchor, ConnectCapabilities, ConnectResponse, CreateFlowOptions, CreateStudySubjectInput, CurrentAction, DialogHost, FinishActionRequest, FinishActionResponse, FinishedSummary, Flow, FlowCredential, FlowSessionState, FlowState, FlowUser, FlowWidgetHandle, FlowWidgetName, FocusApi, FocusPhase, FocusSessionEvent, FocusSessionState, ListStudySessionsOptions, ListStudySubjectsOptions, LoginOptions, MethodName, MountFlowWidgetOptions, MountProfileOptions, OpenProfileOptions, ProfileButtonHandle, ProfilePosition, RoomMemberEntry, Session, StartActionInput, StartActionResponse, StartActionResult, StartFocusRequest, StudyApi, StudyCatalog, StudyCategory, StudyCategoryTotals, StudyField, StudyRange, StudySearchHit, StudySessionEntry, StudySessionPage, StudySubject, StudySubjectList, StudySubjectRefusal, StudySubjectRemovalResponse, StudySubjectResponse, StudySubjectTotals, StudySummary, UpdateStudySubjectInput, UserActionSurface, XpEstimateContext, XpMode, } from "../types";
|
|
2
|
+
export type { AccessKeyOptions, ActionCatalogEntry, ActionCatalogResponse, ActionCurve, ActionEventRequest, ActionEventResponse, ActionSubjectRef, ActionSurface, ActionsApi, ActionTransitionKind, ActiveAction, ActiveActionResponse, ActiveSessionConflict, AdditionalSession, Address, CatalogFieldRef, ClockAnchor, ConnectCapabilities, ConnectResponse, CreateFlowOptions, CreateStudySubjectInput, CurrentAction, DialogHost, FinishActionRequest, FinishActionResponse, FinishedSummary, Flow, FlowCredential, FlowSessionState, FlowState, FlowUser, FlowWidgetHandle, FlowWidgetName, FocusApi, FocusPhase, FocusSessionEvent, FocusSessionState, ListStudySessionsOptions, ListStudySubjectsOptions, LoginOptions, MarkNotificationsReadInput, MethodName, MountFlowWidgetOptions, MountProfileOptions, NotificationCounts, NotificationEntry, NotificationPage, NotificationReadResult, NotificationRefusal, OpenProfileOptions, ProfileButtonHandle, ProfilePosition, RoomMemberEntry, Session, StartActionInput, StartActionResponse, StartActionResult, StartFocusRequest, StudyApi, StudyCatalog, StudyCategory, StudyCategoryTotals, StudyField, StudyRange, StudySearchHit, StudySessionEntry, StudySessionPage, StudySubject, StudySubjectList, StudySubjectRefusal, StudySubjectRemovalResponse, StudySubjectResponse, StudySubjectTotals, StudySummary, UpdateStudySubjectInput, UserActionSurface, XpEstimateContext, XpMode, } from "../types";
|
|
3
3
|
export { consistencyCurve, estimateActionXp, FLOW_SCORE_MAX, flowMultiplier, } from "../xp/curve";
|
|
4
4
|
export { ActionsRequestError, createActionsApi, isActiveSessionConflict, } from "./actions";
|
|
5
5
|
export { createFlow, getFlow, requireFlow, resetFlow } from "./create-flow";
|
|
@@ -92,8 +92,11 @@ export function createProfileButton(options) {
|
|
|
92
92
|
avatar.textContent = user?.username.slice(0, 1).toUpperCase() ?? "F";
|
|
93
93
|
lastUser = user;
|
|
94
94
|
}
|
|
95
|
-
else if (!refresh || profileRequest)
|
|
95
|
+
else if (!refresh || profileRequest) {
|
|
96
|
+
// Same account: only an explicit refresh refetches, and never on top of
|
|
97
|
+
// a request already in flight.
|
|
96
98
|
return;
|
|
99
|
+
}
|
|
97
100
|
if (!user || user.isGuest)
|
|
98
101
|
return;
|
|
99
102
|
const controller = new AbortController();
|
package/dist/sdk/db/schema.d.ts
CHANGED
|
@@ -4053,6 +4053,166 @@ export declare const securityEvent: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
4053
4053
|
};
|
|
4054
4054
|
dialect: "pg";
|
|
4055
4055
|
}>;
|
|
4056
|
+
export declare const notification: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
4057
|
+
name: "notification";
|
|
4058
|
+
schema: undefined;
|
|
4059
|
+
columns: {
|
|
4060
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
4061
|
+
name: "id";
|
|
4062
|
+
tableName: "notification";
|
|
4063
|
+
dataType: "string";
|
|
4064
|
+
columnType: "PgText";
|
|
4065
|
+
data: string;
|
|
4066
|
+
driverParam: string;
|
|
4067
|
+
notNull: true;
|
|
4068
|
+
hasDefault: false;
|
|
4069
|
+
isPrimaryKey: true;
|
|
4070
|
+
isAutoincrement: false;
|
|
4071
|
+
hasRuntimeDefault: false;
|
|
4072
|
+
enumValues: [string, ...string[]];
|
|
4073
|
+
baseColumn: never;
|
|
4074
|
+
identity: undefined;
|
|
4075
|
+
generated: undefined;
|
|
4076
|
+
}, {}, {}>;
|
|
4077
|
+
userId: import("drizzle-orm/pg-core").PgColumn<{
|
|
4078
|
+
name: "user_id";
|
|
4079
|
+
tableName: "notification";
|
|
4080
|
+
dataType: "string";
|
|
4081
|
+
columnType: "PgText";
|
|
4082
|
+
data: string;
|
|
4083
|
+
driverParam: string;
|
|
4084
|
+
notNull: true;
|
|
4085
|
+
hasDefault: false;
|
|
4086
|
+
isPrimaryKey: false;
|
|
4087
|
+
isAutoincrement: false;
|
|
4088
|
+
hasRuntimeDefault: false;
|
|
4089
|
+
enumValues: [string, ...string[]];
|
|
4090
|
+
baseColumn: never;
|
|
4091
|
+
identity: undefined;
|
|
4092
|
+
generated: undefined;
|
|
4093
|
+
}, {}, {}>;
|
|
4094
|
+
type: import("drizzle-orm/pg-core").PgColumn<{
|
|
4095
|
+
name: "type";
|
|
4096
|
+
tableName: "notification";
|
|
4097
|
+
dataType: "string";
|
|
4098
|
+
columnType: "PgText";
|
|
4099
|
+
data: string;
|
|
4100
|
+
driverParam: string;
|
|
4101
|
+
notNull: true;
|
|
4102
|
+
hasDefault: false;
|
|
4103
|
+
isPrimaryKey: false;
|
|
4104
|
+
isAutoincrement: false;
|
|
4105
|
+
hasRuntimeDefault: false;
|
|
4106
|
+
enumValues: [string, ...string[]];
|
|
4107
|
+
baseColumn: never;
|
|
4108
|
+
identity: undefined;
|
|
4109
|
+
generated: undefined;
|
|
4110
|
+
}, {}, {}>;
|
|
4111
|
+
subjectType: import("drizzle-orm/pg-core").PgColumn<{
|
|
4112
|
+
name: "subject_type";
|
|
4113
|
+
tableName: "notification";
|
|
4114
|
+
dataType: "string";
|
|
4115
|
+
columnType: "PgText";
|
|
4116
|
+
data: string;
|
|
4117
|
+
driverParam: string;
|
|
4118
|
+
notNull: true;
|
|
4119
|
+
hasDefault: false;
|
|
4120
|
+
isPrimaryKey: false;
|
|
4121
|
+
isAutoincrement: false;
|
|
4122
|
+
hasRuntimeDefault: false;
|
|
4123
|
+
enumValues: [string, ...string[]];
|
|
4124
|
+
baseColumn: never;
|
|
4125
|
+
identity: undefined;
|
|
4126
|
+
generated: undefined;
|
|
4127
|
+
}, {}, {}>;
|
|
4128
|
+
subjectId: import("drizzle-orm/pg-core").PgColumn<{
|
|
4129
|
+
name: "subject_id";
|
|
4130
|
+
tableName: "notification";
|
|
4131
|
+
dataType: "string";
|
|
4132
|
+
columnType: "PgText";
|
|
4133
|
+
data: string;
|
|
4134
|
+
driverParam: string;
|
|
4135
|
+
notNull: false;
|
|
4136
|
+
hasDefault: false;
|
|
4137
|
+
isPrimaryKey: false;
|
|
4138
|
+
isAutoincrement: false;
|
|
4139
|
+
hasRuntimeDefault: false;
|
|
4140
|
+
enumValues: [string, ...string[]];
|
|
4141
|
+
baseColumn: never;
|
|
4142
|
+
identity: undefined;
|
|
4143
|
+
generated: undefined;
|
|
4144
|
+
}, {}, {}>;
|
|
4145
|
+
occasion: import("drizzle-orm/pg-core").PgColumn<{
|
|
4146
|
+
name: "occasion";
|
|
4147
|
+
tableName: "notification";
|
|
4148
|
+
dataType: "string";
|
|
4149
|
+
columnType: "PgText";
|
|
4150
|
+
data: string;
|
|
4151
|
+
driverParam: string;
|
|
4152
|
+
notNull: true;
|
|
4153
|
+
hasDefault: true;
|
|
4154
|
+
isPrimaryKey: false;
|
|
4155
|
+
isAutoincrement: false;
|
|
4156
|
+
hasRuntimeDefault: false;
|
|
4157
|
+
enumValues: [string, ...string[]];
|
|
4158
|
+
baseColumn: never;
|
|
4159
|
+
identity: undefined;
|
|
4160
|
+
generated: undefined;
|
|
4161
|
+
}, {}, {}>;
|
|
4162
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
4163
|
+
name: "created_at";
|
|
4164
|
+
tableName: "notification";
|
|
4165
|
+
dataType: "date";
|
|
4166
|
+
columnType: "PgTimestamp";
|
|
4167
|
+
data: Date;
|
|
4168
|
+
driverParam: string;
|
|
4169
|
+
notNull: true;
|
|
4170
|
+
hasDefault: true;
|
|
4171
|
+
isPrimaryKey: false;
|
|
4172
|
+
isAutoincrement: false;
|
|
4173
|
+
hasRuntimeDefault: false;
|
|
4174
|
+
enumValues: undefined;
|
|
4175
|
+
baseColumn: never;
|
|
4176
|
+
identity: undefined;
|
|
4177
|
+
generated: undefined;
|
|
4178
|
+
}, {}, {}>;
|
|
4179
|
+
readAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
4180
|
+
name: "read_at";
|
|
4181
|
+
tableName: "notification";
|
|
4182
|
+
dataType: "date";
|
|
4183
|
+
columnType: "PgTimestamp";
|
|
4184
|
+
data: Date;
|
|
4185
|
+
driverParam: string;
|
|
4186
|
+
notNull: false;
|
|
4187
|
+
hasDefault: false;
|
|
4188
|
+
isPrimaryKey: false;
|
|
4189
|
+
isAutoincrement: false;
|
|
4190
|
+
hasRuntimeDefault: false;
|
|
4191
|
+
enumValues: undefined;
|
|
4192
|
+
baseColumn: never;
|
|
4193
|
+
identity: undefined;
|
|
4194
|
+
generated: undefined;
|
|
4195
|
+
}, {}, {}>;
|
|
4196
|
+
seenAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
4197
|
+
name: "seen_at";
|
|
4198
|
+
tableName: "notification";
|
|
4199
|
+
dataType: "date";
|
|
4200
|
+
columnType: "PgTimestamp";
|
|
4201
|
+
data: Date;
|
|
4202
|
+
driverParam: string;
|
|
4203
|
+
notNull: false;
|
|
4204
|
+
hasDefault: false;
|
|
4205
|
+
isPrimaryKey: false;
|
|
4206
|
+
isAutoincrement: false;
|
|
4207
|
+
hasRuntimeDefault: false;
|
|
4208
|
+
enumValues: undefined;
|
|
4209
|
+
baseColumn: never;
|
|
4210
|
+
identity: undefined;
|
|
4211
|
+
generated: undefined;
|
|
4212
|
+
}, {}, {}>;
|
|
4213
|
+
};
|
|
4214
|
+
dialect: "pg";
|
|
4215
|
+
}>;
|
|
4056
4216
|
export declare const accountErasure: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
4057
4217
|
name: "account_erasure";
|
|
4058
4218
|
schema: undefined;
|
package/dist/sdk/db/schema.js
CHANGED
|
@@ -692,6 +692,83 @@ export const securityEvent = pgTable("security_event", {
|
|
|
692
692
|
}, (table) => [
|
|
693
693
|
index("security_event_user_created_at_idx").on(table.userId, table.createdAt),
|
|
694
694
|
]);
|
|
695
|
+
// The per-recipient notification index (AUTH-351): one address row per user per
|
|
696
|
+
// thing they were told about, recording WHO was addressed, of WHAT type, about
|
|
697
|
+
// WHICH referent and WHEN — and nothing a viewer reads. There is deliberately
|
|
698
|
+
// no title, body, href or payload column: every rendered field is produced by
|
|
699
|
+
// that type's resolver from the live source at read time, so a referent that
|
|
700
|
+
// was deleted or cancelled, or that this viewer may no longer see, suppresses
|
|
701
|
+
// the entry instead of rendering it. A row carrying a copy of the text would
|
|
702
|
+
// outlive its source and keep showing what nobody may see any more, which is
|
|
703
|
+
// the guarantee talk/server/inbox.ts gets for free from indexing on the
|
|
704
|
+
// source's own table and auth's heterogeneous sources have to keep by
|
|
705
|
+
// construction.
|
|
706
|
+
//
|
|
707
|
+
// Keyed on user.id (the JWT sub) so rows survive the in-place guest->full
|
|
708
|
+
// upgrade, and cascade on user delete. Guests hold notifications like anyone
|
|
709
|
+
// else — with no email and no linked Discord, in-app is the only channel that
|
|
710
|
+
// can ever reach them.
|
|
711
|
+
//
|
|
712
|
+
// `subject_id` is null for a type addressed at the account rather than at a
|
|
713
|
+
// row. `occasion` is the second address axis inside one subject: two notices
|
|
714
|
+
// about the same referent that are genuinely different notices rather than a
|
|
715
|
+
// retry of one (a room event's T-60, T-10 and start reminders all address the
|
|
716
|
+
// same occurrence). It is an opaque address token, never content -- a resolver
|
|
717
|
+
// still renders every field from the live source, and an empty string is the
|
|
718
|
+
// default for a type that addresses a subject only once.
|
|
719
|
+
//
|
|
720
|
+
// `read_at` is set only by the mark-read route, `seen_at` by the panel-open
|
|
721
|
+
// route, and a read row is always also seen, which is what lets one partial
|
|
722
|
+
// index answer both counts.
|
|
723
|
+
//
|
|
724
|
+
// The three instants are `timestamp(3)`: a JS Date is millisecond-resolution,
|
|
725
|
+
// so a column at Postgres's default microsecond resolution cannot be addressed
|
|
726
|
+
// exactly by the keyset cursor or by a `before` echoed back from a response.
|
|
727
|
+
// The default is `now() at time zone 'utc'` rather than plain `now()`, which a
|
|
728
|
+
// naive column resolves through the session TimeZone -- drizzle reads a naive
|
|
729
|
+
// column back as UTC in every case, so a non-UTC session would silently shift
|
|
730
|
+
// every row against the app clock that every predicate here uses.
|
|
731
|
+
export const notification = pgTable("notification", {
|
|
732
|
+
id: text("id").primaryKey(),
|
|
733
|
+
userId: text("user_id")
|
|
734
|
+
.notNull()
|
|
735
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
736
|
+
type: text("type").notNull(),
|
|
737
|
+
subjectType: text("subject_type").notNull(),
|
|
738
|
+
subjectId: text("subject_id"),
|
|
739
|
+
occasion: text("occasion").notNull().default(""),
|
|
740
|
+
createdAt: timestamp("created_at", { precision: 3 })
|
|
741
|
+
.notNull()
|
|
742
|
+
.default(sql `(now() at time zone 'utc')`),
|
|
743
|
+
readAt: timestamp("read_at", { precision: 3 }),
|
|
744
|
+
seenAt: timestamp("seen_at", { precision: 3 }),
|
|
745
|
+
}, (table) => [
|
|
746
|
+
// The keyset page: WHERE user_id = ? ORDER BY created_at DESC, id DESC,
|
|
747
|
+
// which is also the order the (created_at, id) cursor resumes from.
|
|
748
|
+
// Deliberately ASC, like security_event_user_created_at_idx: a DESC index
|
|
749
|
+
// is built NULLS LAST while a bare `ORDER BY ... DESC` asks for NULLS
|
|
750
|
+
// FIRST, so the pathkeys never match and the planner sorts instead of
|
|
751
|
+
// walking the index. Ascending, it serves the page as a backward scan.
|
|
752
|
+
index("notification_user_created_at_idx").on(table.userId, table.createdAt, table.id),
|
|
753
|
+
// The badge count — kept partial so it stays small no matter how much
|
|
754
|
+
// history a long-lived account accumulates. Unseen rows are a subset of
|
|
755
|
+
// unread ones (marking read also stamps seen), so this one index serves
|
|
756
|
+
// both numbers, and the panel-open stamp carries a read_at term for the
|
|
757
|
+
// same reason: `seen_at is null` on its own is not a predicate anything
|
|
758
|
+
// here can serve.
|
|
759
|
+
index("notification_user_unread_idx")
|
|
760
|
+
.on(table.userId, table.createdAt)
|
|
761
|
+
.where(sql `${table.readAt} is null`),
|
|
762
|
+
// Idempotency: a producer that retries a fan-out writes the same address
|
|
763
|
+
// twice and the second insert must be a no-op, never a duplicate entry.
|
|
764
|
+
// Indexed on coalesce() rather than on the column because Postgres treats
|
|
765
|
+
// NULLs as distinct, which would let an account-scoped type (null subject)
|
|
766
|
+
// duplicate freely; an empty string is not a valid id, so it can never
|
|
767
|
+
// collide with a real one. `occasion` is part of the key, so the three
|
|
768
|
+
// reminders one occurrence sends are three addresses while a retry of any
|
|
769
|
+
// one of them still collapses into the row it already wrote.
|
|
770
|
+
uniqueIndex("notification_user_type_subject_idx").on(table.userId, table.type, table.subjectType, sql `coalesce(${table.subjectId}, '')`, table.occasion),
|
|
771
|
+
]);
|
|
695
772
|
// The erasure ledger: one row per deleted account, appended in the same
|
|
696
773
|
// transaction that deletes the user row (src/account-erasure.ts). `user_id` is
|
|
697
774
|
// the sub every downstream service keyed its data on and is deliberately NOT a
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* promotes to a `cosmetic` table when non-engineers must edit it live.
|
|
17
17
|
*/
|
|
18
18
|
import { isString } from "../json";
|
|
19
|
+
import earItems from "./ear-items.json";
|
|
19
20
|
import hairItems from "./hair-items.json";
|
|
20
21
|
/**
|
|
21
22
|
* Region pairs that physically overlap, declared once in ONE direction and
|
|
@@ -60,6 +61,7 @@ const OVERLAPS = (() => {
|
|
|
60
61
|
*/
|
|
61
62
|
const SLOTS = {
|
|
62
63
|
hair: { label: "Hair", required: true },
|
|
64
|
+
ears: { label: "Ears", required: true },
|
|
63
65
|
hat: { label: "Headwear", required: false },
|
|
64
66
|
face: { label: "Face", required: false },
|
|
65
67
|
top: { label: "Top", required: true },
|
|
@@ -102,6 +104,13 @@ export function catalogOf(items) {
|
|
|
102
104
|
* there names nothing the renderer can draw.
|
|
103
105
|
*/
|
|
104
106
|
export const COSMETICS = [
|
|
107
|
+
...earItems.map((item) => ({
|
|
108
|
+
...item,
|
|
109
|
+
slot: "ears",
|
|
110
|
+
regions: item.id === "ears_original" ? [] : ["ears"],
|
|
111
|
+
hides: item.id === "ears_original" ? [] : ["ears"],
|
|
112
|
+
paintable: [],
|
|
113
|
+
})),
|
|
105
114
|
...hairItems.map((item) => ({
|
|
106
115
|
...item,
|
|
107
116
|
slot: "hair",
|
|
@@ -193,6 +202,7 @@ export const CATALOG = catalogOf(COSMETICS);
|
|
|
193
202
|
*/
|
|
194
203
|
export const STARTING_OUTFIT = {
|
|
195
204
|
hair: "hair_original",
|
|
205
|
+
ears: "ears_original",
|
|
196
206
|
hat: "",
|
|
197
207
|
face: "",
|
|
198
208
|
top: "hoodie",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "ears_original",
|
|
4
|
+
"name": "Soft point",
|
|
5
|
+
"description": "Compact ears with gently tapered tips and a soft, rounded shape."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"id": "ears_small_round",
|
|
9
|
+
"name": "Small rounded",
|
|
10
|
+
"description": "Small rounded ears tucked close to the sides of the head."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "ears_short_point",
|
|
14
|
+
"name": "Short pointed",
|
|
15
|
+
"description": "Compact pointed ears with a soft rim and a slight upward tilt."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "ears_long_swept",
|
|
19
|
+
"name": "Elf ears",
|
|
20
|
+
"description": "Long pointed elf ears sweep outward and upward from the sides of the head."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "ears_cat_upright",
|
|
24
|
+
"name": "Upright cat",
|
|
25
|
+
"description": "Two upright triangular cat ears with broad bases and inset centers."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "ears_bear_round",
|
|
29
|
+
"name": "Rounded bear",
|
|
30
|
+
"description": "A pair of soft round bear ears perched on the top of the head."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "ears_rabbit_upright",
|
|
34
|
+
"name": "Upright rabbit",
|
|
35
|
+
"description": "Tall upright rabbit ears with rounded tips and long inset centers."
|
|
36
|
+
}
|
|
37
|
+
]
|
|
@@ -15,13 +15,24 @@ export const NOTIFICATION_SETTINGS = [
|
|
|
15
15
|
key: "room_events",
|
|
16
16
|
group: "events",
|
|
17
17
|
label: "Event reminders",
|
|
18
|
-
|
|
18
|
+
// Two honesty problems, both fixed in the wording rather than by widening
|
|
19
|
+
// the enum (offering email is AUTH-177). It used to promise "In the menu",
|
|
20
|
+
// a surface that did not exist and a delivery that never happened; and it
|
|
21
|
+
// read as per-person routing, which the Discord half is not — a room wired
|
|
22
|
+
// to Discord posts one announcement to its channel for everyone in it, and
|
|
23
|
+
// no per-user preference can suppress somebody else's room's post.
|
|
24
|
+
description: "How you hear about an event you marked Interested in. A room wired to Discord announces every event in its channel for everyone, whichever of these you pick.",
|
|
19
25
|
type: "enum",
|
|
20
26
|
default: "in_app",
|
|
27
|
+
// The stored values are load-bearing: they are in every user_settings blob
|
|
28
|
+
// and `in_app` is the channel name on the dispatch ledger.
|
|
21
29
|
options: [
|
|
22
|
-
{ value: "in_app", label: "In
|
|
23
|
-
{
|
|
24
|
-
|
|
30
|
+
{ value: "in_app", label: "In your Flow notifications" },
|
|
31
|
+
{
|
|
32
|
+
value: "discord",
|
|
33
|
+
label: "In the room's Discord — or here, if the room has none",
|
|
34
|
+
},
|
|
35
|
+
{ value: "off", label: "Don't remind me" },
|
|
25
36
|
],
|
|
26
37
|
},
|
|
27
38
|
];
|
|
@@ -231,6 +231,22 @@ export type AccountExportGameEvent = {
|
|
|
231
231
|
reason: string | null;
|
|
232
232
|
detail: RawRecord | null;
|
|
233
233
|
};
|
|
234
|
+
/**
|
|
235
|
+
* One in-app notification the account was addressed with. Deliberately the
|
|
236
|
+
* stored row and nothing more: the table holds no title, body or href, so an
|
|
237
|
+
* export cannot carry content the live source may since have withdrawn.
|
|
238
|
+
*/
|
|
239
|
+
export type AccountExportNotification = {
|
|
240
|
+
id: string;
|
|
241
|
+
type: string;
|
|
242
|
+
subjectType: string;
|
|
243
|
+
subjectId: string | null;
|
|
244
|
+
/** Which notice about that subject it was; empty for a once-only type. */
|
|
245
|
+
occasion: string;
|
|
246
|
+
createdAt: string;
|
|
247
|
+
readAt: string | null;
|
|
248
|
+
seenAt: string | null;
|
|
249
|
+
};
|
|
234
250
|
/** Where the rest of a person's Flow data lives, so one export names every
|
|
235
251
|
* service that has to be asked. */
|
|
236
252
|
export type AccountExportOtherService = {
|
|
@@ -273,6 +289,7 @@ export type AccountExport = {
|
|
|
273
289
|
rooms: AccountExportRooms;
|
|
274
290
|
roomEvents: AccountExportRoomEvents;
|
|
275
291
|
gameEvents: AccountExportGameEvent[];
|
|
292
|
+
notifications: AccountExportNotification[];
|
|
276
293
|
securityActivity: SecurityActivityEntry[];
|
|
277
294
|
otherServices: AccountExportOtherService[];
|
|
278
295
|
/** Dotted names of every collection that hit the row cap (newest rows kept). */
|
package/dist/sdk/types/auth.d.ts
CHANGED
|
@@ -175,3 +175,21 @@ export type AccountRegistration = {
|
|
|
175
175
|
walletAddress: string | null;
|
|
176
176
|
credential: FlowCredential | null;
|
|
177
177
|
};
|
|
178
|
+
export type AuthorizationTransaction = {
|
|
179
|
+
expiresAt: number;
|
|
180
|
+
accessKeyHash?: string;
|
|
181
|
+
credentialId?: string;
|
|
182
|
+
audience: string;
|
|
183
|
+
signUp: boolean;
|
|
184
|
+
guestToken?: string;
|
|
185
|
+
currentUser: {
|
|
186
|
+
id: string;
|
|
187
|
+
username: string;
|
|
188
|
+
} | null;
|
|
189
|
+
};
|
|
190
|
+
export type AuthPageBootstrap = {
|
|
191
|
+
config: AuthConfig;
|
|
192
|
+
transaction?: string;
|
|
193
|
+
context?: AuthorizationTransaction;
|
|
194
|
+
error?: string;
|
|
195
|
+
};
|
|
@@ -81,7 +81,7 @@ export type CosmeticMaterial = (typeof COSMETIC_MATERIALS)[number];
|
|
|
81
81
|
* that shrank whenever a slot emptied would churn the contract every time
|
|
82
82
|
* content lands.
|
|
83
83
|
*/
|
|
84
|
-
export type CosmeticSlot = "hat" | "hair" | "face" | "top" | "bottom" | "feet";
|
|
84
|
+
export type CosmeticSlot = "hat" | "hair" | "ears" | "face" | "top" | "bottom" | "feet";
|
|
85
85
|
/** One item in the catalog. */
|
|
86
86
|
export interface CosmeticItem {
|
|
87
87
|
id: string;
|
|
@@ -41,11 +41,14 @@ export interface AuthEventRecord {
|
|
|
41
41
|
durationMs: number | null;
|
|
42
42
|
traceId: string | null;
|
|
43
43
|
/** Room-event scheduler fields: which series and dated instance a transition
|
|
44
|
-
* or dispatch was about, and for a dispatch, which reminder
|
|
45
|
-
*
|
|
44
|
+
* or dispatch was about, and for a dispatch, which reminder went out on
|
|
45
|
+
* which channel and how many rows it wrote. `channel` is explicit because
|
|
46
|
+
* the alternative was reading a `:discord` suffix off `dispatchKind`, which
|
|
47
|
+
* every consumer then has to parse to tell two channels apart. */
|
|
46
48
|
roomEventId: string | null;
|
|
47
49
|
occurrenceId: string | null;
|
|
48
50
|
dispatchKind: string | null;
|
|
51
|
+
channel: string | null;
|
|
49
52
|
recipients: number | null;
|
|
50
53
|
/** Account-lifecycle fields: whether the account was a guest, and what an
|
|
51
54
|
* erasure or export touched, as per-section row counts. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { AccountDeleteRequest, AccountDeleteResponse, AccountErasureCounts, AccountErasureEntry, AccountErasureFeed, AccountErasureResult, AccountExport, AccountExportAccount, AccountExportActionEvent, AccountExportActionSession, AccountExportAttendance, AccountExportAuthoredEvent, AccountExportCharacterStateTime, AccountExportFollow, AccountExportGameEvent, AccountExportOtherService, AccountExportOwnedRoom, AccountExportPasskey, AccountExportRoomEvents, AccountExportRoomMembership, AccountExportRoomPlayerState, AccountExportRoomPresence, AccountExportRoomRestriction, AccountExportRoomSettings, AccountExportRooms, AccountExportRsvp, AccountExportSettingsSurface, AccountExportStudySubject, AccountExportXpGrant, AccountExportXpSummary, } from "./account-data";
|
|
1
|
+
export type { AccountDeleteRequest, AccountDeleteResponse, AccountErasureCounts, AccountErasureEntry, AccountErasureFeed, AccountErasureResult, AccountExport, AccountExportAccount, AccountExportActionEvent, AccountExportActionSession, AccountExportAttendance, AccountExportAuthoredEvent, AccountExportCharacterStateTime, AccountExportFollow, AccountExportGameEvent, AccountExportNotification, AccountExportOtherService, AccountExportOwnedRoom, AccountExportPasskey, AccountExportRoomEvents, AccountExportRoomMembership, AccountExportRoomPlayerState, AccountExportRoomPresence, AccountExportRoomRestriction, AccountExportRoomSettings, AccountExportRooms, AccountExportRsvp, AccountExportSettingsSurface, AccountExportStudySubject, AccountExportXpGrant, AccountExportXpSummary, } from "./account-data";
|
|
2
2
|
export type { ActionCurve, ActionEventRequest, ActionEventResponse, ActionsApi, ActiveActionState, FinishActionRequest, ListStudySessionsOptions, ListStudySubjectsOptions, StartActionInput, StartActionResult, StudyApi, StudySubjectResponse, UseActiveActionOptions, XpEstimateContext, } from "./actions";
|
|
3
3
|
export type { AtprotoCompletion, AtprotoStartRequest, AtprotoStatus, } from "./atproto";
|
|
4
|
-
export type { AccountRegistration, AccountSession, AccountSessionsResponse, AdditionalSession, AuthConfig, AuthResponse, AuthResponseWithWebAuthn, BioNormalization, BioRejection, BioWriteRefusal, BioWriteResult, FlowCredential, FlowUser, PasskeyEnrollment, PasskeyPluginOptions, RevokeAllSessionsResponse, RevokeSessionResponse, SecurityActivityEntry, SecurityActivityResponse, SessionUser, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
|
|
4
|
+
export type { AccountRegistration, AccountSession, AccountSessionsResponse, AdditionalSession, AuthConfig, AuthorizationTransaction, AuthPageBootstrap, AuthResponse, AuthResponseWithWebAuthn, BioNormalization, BioRejection, BioWriteRefusal, BioWriteResult, FlowCredential, FlowUser, PasskeyEnrollment, PasskeyPluginOptions, RevokeAllSessionsResponse, RevokeSessionResponse, SecurityActivityEntry, SecurityActivityResponse, SessionUser, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
|
|
5
5
|
export type { BodyRegion, CosmeticItem, CosmeticMaterial, CosmeticPaint, CosmeticSlot, EquipConflict, EquippedCosmetic, EquippedCosmetics, EquipRegion, PaintSlot, SettingsConflict, } from "./cosmetics";
|
|
6
6
|
export { BODY_REGIONS, COSMETIC_MATERIALS } from "./cosmetics";
|
|
7
7
|
export type { AccountDataDownload, AccountDataIdentity, BoundaryError, DialogCustomFeatures, DialogCustomLabels, DialogError, DialogReferrer, DialogState, Loadable, ProfileData, ProfileIdentity, SignInWithEmailProps, SignUpProps, } from "./dialog";
|
|
@@ -11,6 +11,7 @@ export type { GameEventReport, GameLifecycleEventName, GameLifecycleFeedEvent, G
|
|
|
11
11
|
export { GAME_LIFECYCLE_EVENT_NAMES } from "./game";
|
|
12
12
|
export type { AccountProfileData } from "./landing";
|
|
13
13
|
export type { Bridge, BridgeParameters, FlowAccount, FlowRemote, FlowRemoteConfig, FromWindowOptions, MessageResponse, Messenger, OneOf, Payload, QueuedRequest, ReadyOptions, RemoteFlowState, RemoteState, Schema, Storage, Topic, WithReady, } from "./messenger";
|
|
14
|
+
export type { MarkNotificationsReadInput, NotificationCounts, NotificationEntry, NotificationPage, NotificationReadResult, NotificationRefusal, } from "./notifications";
|
|
14
15
|
export type { OtpVerificationFailure, OtpVerificationResult, VerificationAdapter, } from "./otp";
|
|
15
16
|
export type { Call, ConnectCapabilities, ConnectResponse, MethodName, MethodParams, MethodResult, RestoreResponse, RpcRequest, SendCallsParams, SendCallsRequest, SendCallsResponse, SendTransactionParams, SendTransactionRequest, SendTransactionResponse, SignMessageRequest, SignMessageResponse, SignTypedDataRequest, SignTypedDataResponse, TransactionArgs, TypedData, TypedDataDomain, TypedDataField, } from "./protocol";
|
|
16
17
|
export { isPersonalSignParams, isSendCallsParams, isSendTransactionParams, } from "./protocol";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-app notifications — the shapes `/api/notifications` speaks (AUTH-351).
|
|
3
|
+
*
|
|
4
|
+
* A stored notification is an address row carrying no content, so every field
|
|
5
|
+
* of a {@link NotificationEntry} below is rendered at read time from the live
|
|
6
|
+
* source by that type's resolver. An entry therefore only exists while its
|
|
7
|
+
* referent does and while this viewer may still see it; nothing here is a copy
|
|
8
|
+
* of anything, and a client must not cache an entry as a durable record.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* One resolved notification, ready to render.
|
|
12
|
+
*
|
|
13
|
+
* `occurredAt` is the *source's* own instant (a reminder's occurrence start),
|
|
14
|
+
* which is what a panel formats relative time from — the server never bakes
|
|
15
|
+
* "in 10 minutes" into a polled response. `createdAt` is when the user was
|
|
16
|
+
* addressed, and it is what the page is ordered and paged on. `href` is a path
|
|
17
|
+
* inside the consuming app (`/events/<id>`), never an absolute URL: auth does
|
|
18
|
+
* not know which surface is rendering it.
|
|
19
|
+
*/
|
|
20
|
+
export interface NotificationEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
type: string;
|
|
23
|
+
title: string;
|
|
24
|
+
body: string | null;
|
|
25
|
+
href: string | null;
|
|
26
|
+
/** A coarse family name the surface maps to its own icon set. */
|
|
27
|
+
icon: string;
|
|
28
|
+
occurredAt: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
readAt: string | null;
|
|
31
|
+
seenAt: string | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Response of `GET /api/notifications`. A full page is returned whenever that
|
|
35
|
+
* many visible entries exist, so a suppressed row never eats a slot; feed
|
|
36
|
+
* `nextCursor` back as `?cursor=` until it is null.
|
|
37
|
+
*/
|
|
38
|
+
export interface NotificationPage {
|
|
39
|
+
notifications: NotificationEntry[];
|
|
40
|
+
nextCursor: string | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Response of `GET /api/notifications/unread-count`. `unseen` is the bell's
|
|
44
|
+
* badge, cleared by `POST /api/notifications/seen` when the panel opens; a
|
|
45
|
+
* list read writes nothing. `unread` is the per-entry styling count and is
|
|
46
|
+
* only cleared by `POST /api/notifications/read`. Both are counted over stored
|
|
47
|
+
* rows without resolving them, so either may count an entry that a list read
|
|
48
|
+
* would suppress.
|
|
49
|
+
*/
|
|
50
|
+
export interface NotificationCounts {
|
|
51
|
+
unread: number;
|
|
52
|
+
unseen: number;
|
|
53
|
+
}
|
|
54
|
+
/** Response of `POST /api/notifications/read`: rows this call changed, plus the fresh counts. */
|
|
55
|
+
export interface NotificationReadResult extends NotificationCounts {
|
|
56
|
+
updated: number;
|
|
57
|
+
}
|
|
58
|
+
/** Body of `POST /api/notifications/read` — exactly one of the two. */
|
|
59
|
+
export interface MarkNotificationsReadInput {
|
|
60
|
+
ids?: string[];
|
|
61
|
+
/** ISO instant; marks every notification addressed at or before it. */
|
|
62
|
+
before?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Machine-readable refusal codes the notification routes answer with. */
|
|
65
|
+
export type NotificationRefusal = "invalid_cursor";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-app notifications — the shapes `/api/notifications` speaks (AUTH-351).
|
|
3
|
+
*
|
|
4
|
+
* A stored notification is an address row carrying no content, so every field
|
|
5
|
+
* of a {@link NotificationEntry} below is rendered at read time from the live
|
|
6
|
+
* source by that type's resolver. An entry therefore only exists while its
|
|
7
|
+
* referent does and while this viewer may still see it; nothing here is a copy
|
|
8
|
+
* of anything, and a client must not cache an entry as a durable record.
|
|
9
|
+
*/
|