wirejs-resources 0.1.105 → 0.1.107-payments

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.
@@ -1,4 +1,5 @@
1
1
  import { CookieJar } from "./cookie-jar.js";
2
+ import { SystemAttribute } from "../resources/system-attribute.js";
2
3
  type ApiMethod = (...args: any) => any;
3
4
  type ApiNamespace = {
4
5
  [K in string]: ApiMethod | ApiNamespace;
@@ -8,14 +9,34 @@ export type ContextfulApiNamespace<T> = {
8
9
  [K in keyof T]: T[K] extends ApiMethod ? ContextfulApiMethod<T[K]> : ContextfulApiNamespace<T[K]>;
9
10
  };
10
11
  export type ContextWrapped<T extends ApiNamespace | ApiMethod> = T extends ApiMethod ? ContextfulApiMethod<T> : ContextfulApiNamespace<T>;
12
+ export type SystemInfo = Record<string, SystemAttribute>;
11
13
  export declare function withContext<T extends ApiMethod | ApiNamespace>(contextWrapper: (context: Context) => T, path?: string[]): ContextWrapped<T>;
12
14
  export declare function requiresContext(fnOrNS: Object): fnOrNS is (context: Context) => any;
13
15
  export declare class Context {
14
- cookies: CookieJar;
15
- location: URL;
16
- constructor({ cookies, location }: {
16
+ /**
17
+ * `cookies` can be directly manipulated. The response pipeline
18
+ * will ingest changes to cookies when issuing a response.
19
+ */
20
+ readonly cookies: CookieJar;
21
+ readonly httpMethod?: string;
22
+ readonly requestHeaders: Record<string, string | string[]>;
23
+ readonly requestBody?: string;
24
+ responseHeaders: Record<string, string>;
25
+ responseCode: number | undefined;
26
+ private _location;
27
+ private _locationChanged;
28
+ private _runtimeAttributes;
29
+ constructor({ cookies, location, httpMethod, requestHeaders, requestBody, runtimeAttributes, }: {
17
30
  cookies: CookieJar;
18
31
  location: URL;
32
+ httpMethod: string;
33
+ requestHeaders: Record<string, string | string[]>;
34
+ requestBody: string | undefined;
35
+ runtimeAttributes?: SystemAttribute[];
19
36
  });
37
+ get location(): URL;
38
+ set location(url: URL | string);
39
+ get locationIsDirty(): boolean;
40
+ get systemInfo(): MapIterator<SystemAttribute>;
20
41
  }
21
42
  export {};
@@ -1,3 +1,4 @@
1
+ import { SystemAttribute } from "../resources/system-attribute.js";
1
2
  const __requiresContext = '__requiresContext';
2
3
  export function withContext(contextWrapper, path = []) {
3
4
  // first param needs to be a function, which enables `Proxy` to implement `apply()`.
@@ -27,10 +28,46 @@ export function requiresContext(fnOrNS) {
27
28
  return true;
28
29
  }
29
30
  export class Context {
31
+ /**
32
+ * `cookies` can be directly manipulated. The response pipeline
33
+ * will ingest changes to cookies when issuing a response.
34
+ */
30
35
  cookies;
31
- location;
32
- constructor({ cookies, location }) {
36
+ httpMethod;
37
+ requestHeaders;
38
+ requestBody;
39
+ responseHeaders = {};
40
+ responseCode;
41
+ _location;
42
+ _locationChanged = false;
43
+ _runtimeAttributes = [];
44
+ constructor({ cookies, location, httpMethod, requestHeaders, requestBody, runtimeAttributes, }) {
33
45
  this.cookies = cookies;
34
46
  this.location = location;
47
+ this.httpMethod = httpMethod;
48
+ this.requestHeaders = requestHeaders;
49
+ this.requestBody = requestBody;
50
+ this._runtimeAttributes = runtimeAttributes || [];
51
+ }
52
+ get location() {
53
+ return this._location;
54
+ }
55
+ set location(url) {
56
+ if (this._location)
57
+ this._locationChanged = true;
58
+ this._location = new URL(url);
59
+ }
60
+ get locationIsDirty() {
61
+ return this._locationChanged;
62
+ }
63
+ get systemInfo() {
64
+ const all = new Map();
65
+ for (const attr of SystemAttribute.listStaticAttributes()) {
66
+ all.set(attr.absoluteId, attr);
67
+ }
68
+ for (const attr of this._runtimeAttributes) {
69
+ all.set(attr.absoluteId, attr);
70
+ }
71
+ return all.values();
35
72
  }
36
73
  }
@@ -1,4 +1,4 @@
1
- import type { Secret } from '../resources/secret.js';
1
+ import type { Setting } from '../resources/setting.js';
2
2
  import { Cookie, CookieJar } from './cookie-jar.js';
3
3
  import { Resource } from '../resource.js';
4
4
  export declare class SignedCookie<T> {
@@ -6,7 +6,10 @@ export declare class SignedCookie<T> {
6
6
  private name;
7
7
  private signingSecret;
8
8
  private options;
9
- constructor(scope: Resource | string, name: string, signingSecret: Secret, options?: Omit<Cookie, 'value' | 'name'>);
9
+ constructor(scope: Resource | string, name: string, signingSecret: Setting<{
10
+ private: true;
11
+ init: 'random';
12
+ }>, options?: Omit<Cookie, 'value' | 'name'>);
10
13
  get cookieName(): string;
11
14
  read(cookies: CookieJar): Promise<T | null>;
12
15
  write(cookies: CookieJar, value: T | null): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -5,10 +5,13 @@ export { SignedCookie } from './adapters/signed-cookie.js';
5
5
  export { withContext, requiresContext, Context, ContextWrapped } from './adapters/context.js';
6
6
  export { Resource } from './resource.js';
7
7
  export { overrides } from './overrides.js';
8
- export { Secret } from './resources/secret.js';
8
+ export { Setting } from './resources/setting.js';
9
9
  export { DistributedTable, PassThruParser, matchesFilter, indexName } from './resources/distributed-table.js';
10
10
  export type * from './resources/distributed-table.js';
11
11
  export * from './types/index.js';
12
12
  export * from './derived-types.js';
13
13
  export * from './services/realtime.js';
14
14
  export * from './resources/background-job.js';
15
+ export * from './resources/key-value-store.js';
16
+ export * from './resources/endpoint.js';
17
+ export * from './resources/system-attribute.js';
package/dist/index.js CHANGED
@@ -5,9 +5,12 @@ export { SignedCookie } from './adapters/signed-cookie.js';
5
5
  export { withContext, requiresContext, Context } from './adapters/context.js';
6
6
  export { Resource } from './resource.js';
7
7
  export { overrides } from './overrides.js';
8
- export { Secret } from './resources/secret.js';
8
+ export { Setting } from './resources/setting.js';
9
9
  export { DistributedTable, PassThruParser, matchesFilter, indexName } from './resources/distributed-table.js';
10
10
  export * from './types/index.js';
11
11
  export * from './derived-types.js';
12
12
  export * from './services/realtime.js';
13
13
  export * from './resources/background-job.js';
14
+ export * from './resources/key-value-store.js';
15
+ export * from './resources/endpoint.js';
16
+ export * from './resources/system-attribute.js';
@@ -1,16 +1,21 @@
1
- import type { FileService } from "./services/file";
2
1
  import type { AuthenticationService } from "./services/authentication";
3
- import type { Secret } from "./resources/secret";
4
- import type { RealtimeService } from "./services/realtime";
5
2
  import type { BackgroundJob } from "./resources/background-job";
3
+ import type { DistributedTable } from "./resources/distributed-table";
4
+ import type { Endpoint } from "./resources/endpoint";
5
+ import type { FileService } from "./services/file";
6
+ import type { KeyValueStore } from "./resources/key-value-store";
7
+ import type { RealtimeService } from "./services/realtime";
8
+ import type { Setting } from "./resources/setting";
6
9
  /**
7
10
  * Used by hosting providers to provide service overrides.
8
11
  */
9
12
  export declare const overrides: {
10
13
  AuthenticationService?: typeof AuthenticationService;
11
- DistributedTable?: typeof AuthenticationService;
14
+ BackgroundJob?: typeof BackgroundJob;
15
+ DistributedTable?: typeof DistributedTable;
16
+ Endpoint?: typeof Endpoint;
12
17
  FileService?: typeof FileService;
13
- Secret?: typeof Secret;
18
+ KeyValueStore?: typeof KeyValueStore;
14
19
  RealtimeService?: typeof RealtimeService;
15
- BackgroundJob?: typeof BackgroundJob;
20
+ Setting?: typeof Setting;
16
21
  };
@@ -26,7 +26,9 @@ export declare class DistributedTable<const P extends Parser<any>, const T exten
26
26
  });
27
27
  get partitionKeyName(): Key['partition']['field'];
28
28
  get sortKeyName(): 'field' extends keyof Key['sort'] ? (Key['sort']['field'] extends string ? Key['sort']['field'] : undefined) : undefined;
29
- save(item: T): Promise<void>;
29
+ save(item: T, options?: {
30
+ onlyIfNotExists?: boolean;
31
+ }): Promise<void>;
30
32
  saveMany(items: T[]): Promise<void>;
31
33
  delete(item: KindaPretty<RecordKey<T, Key>>): Promise<void>;
32
34
  deleteMany(items: (KindaPretty<RecordKey<T, Key>>)[]): Promise<void>;
@@ -58,4 +60,5 @@ export declare class DistributedTable<const P extends Parser<any>, const T exten
58
60
  where: KeyCondition<DistributedTable<P, T, Key, Indexes>, GivenPartition>;
59
61
  filter?: Filter<Omit<T, IndexFieldNames<DistributedTable<P, T, Key, Indexes>, GivenPartition> & string>>;
60
62
  }): AsyncGenerator<T>;
63
+ isAlreadyExistsError(error: any): boolean;
61
64
  }
@@ -80,9 +80,11 @@ export class DistributedTable extends Resource {
80
80
  }
81
81
  return parts.map(String).join('__') + '.json';
82
82
  }
83
- async save(item) {
83
+ async save(item, options) {
84
84
  const key = this.#getFilename(item);
85
- await this.#fileService.write(key, JSON.stringify(item));
85
+ await this.#fileService.write(key, JSON.stringify(item), {
86
+ onlyIfNotExists: options?.onlyIfNotExists
87
+ });
86
88
  }
87
89
  async saveMany(items) {
88
90
  const promises = items.map(item => {
@@ -125,9 +127,10 @@ export class DistributedTable extends Resource {
125
127
  */
126
128
  async *scan(options) {
127
129
  for await (const filename of this.#fileService.list()) {
130
+ console.log('scanning', filename);
128
131
  const data = await this.#fileService.read(filename);
129
132
  const record = this.parse(JSON.parse(data));
130
- if (!options.filter || matchesFilter(record, options.filter)) {
133
+ if (!options?.filter || matchesFilter(record, options.filter)) {
131
134
  yield record;
132
135
  }
133
136
  }
@@ -157,4 +160,7 @@ export class DistributedTable extends Resource {
157
160
  }
158
161
  });
159
162
  }
163
+ isAlreadyExistsError(error) {
164
+ return this.#fileService.isAlreadyExistsError(error);
165
+ }
160
166
  }
@@ -0,0 +1,42 @@
1
+ import { Resource } from '../resource.js';
2
+ import { Context } from '../adapters/context.js';
3
+ export type EndpointOptions = {
4
+ /**
5
+ * If left unspecified, the path will derived from its `absoluteId`.
6
+ *
7
+ * If specified, the endpoint *may* end in a wildcard (`%`) to handle
8
+ * all requests matching the given prefix.
9
+ *
10
+ * Programmatically created endpoints take precedence over
11
+ */
12
+ path?: string;
13
+ /**
14
+ * The function that will handle requests.
15
+ *
16
+ * Currently, these handlers can only return strings, which means that if
17
+ * your endpoint returns an HTML page that requires other resources, you
18
+ * must also provide those resources manually.
19
+ *
20
+ * @param context
21
+ * @returns
22
+ */
23
+ handle: (context: Context) => string | Promise<string>;
24
+ /**
25
+ * Message explaining the purpose of the path.
26
+ */
27
+ description?: string;
28
+ };
29
+ export declare class Endpoint extends Resource {
30
+ options: EndpointOptions;
31
+ constructor(scope: Resource | string, id: string, options: EndpointOptions);
32
+ static list(): MapIterator<Endpoint>;
33
+ static get(key: string): Endpoint | undefined;
34
+ get description(): string | undefined;
35
+ get path(): string;
36
+ get handle(): (context: Context) => string | Promise<string>;
37
+ /**
38
+ * Constructed full URL of the endpoint based on the configured
39
+ * `wirejs/endpoint-origin` setting.
40
+ */
41
+ determineUrl(): Promise<string>;
42
+ }
@@ -0,0 +1,48 @@
1
+ import { Resource } from '../resource.js';
2
+ import { Setting } from './setting.js';
3
+ import { overrides } from '../overrides.js';
4
+ let endpointOrigin;
5
+ const allEndpoints = new Map();
6
+ function createOriginSetting() {
7
+ return new (overrides.Setting || Setting)('wirejs', 'endpoint-origin', {
8
+ description: 'Base URL modules should broadcast to external services.',
9
+ init: () => 'http://localhost:3000',
10
+ private: false,
11
+ });
12
+ }
13
+ export class Endpoint extends Resource {
14
+ options;
15
+ constructor(scope, id, options) {
16
+ super(scope, id);
17
+ this.options = options;
18
+ allEndpoints.set(this.absoluteId, this);
19
+ endpointOrigin = endpointOrigin || createOriginSetting();
20
+ }
21
+ static list() {
22
+ return allEndpoints.values();
23
+ }
24
+ static get(key) {
25
+ return allEndpoints.get(key);
26
+ }
27
+ get description() {
28
+ return this.options.description;
29
+ }
30
+ get path() {
31
+ const path = this.options.path || this.absoluteId;
32
+ return path.startsWith('/') ? path : `/${path}`;
33
+ }
34
+ get handle() {
35
+ return this.options.handle;
36
+ }
37
+ /**
38
+ * Constructed full URL of the endpoint based on the configured
39
+ * `wirejs/endpoint-origin` setting.
40
+ */
41
+ async determineUrl() {
42
+ const origin = await endpointOrigin?.read();
43
+ const normalizedOrigin = origin?.endsWith('/')
44
+ ? origin.slice(0, origin.length - 1)
45
+ : origin;
46
+ return `${normalizedOrigin}${this.path}`;
47
+ }
48
+ }
@@ -0,0 +1,16 @@
1
+ import { Resource } from '../resource.js';
2
+ import { KindaPretty } from '../types/index.js';
3
+ export declare class KeyValueStore<T> extends Resource {
4
+ #private;
5
+ constructor(scope: Resource | string, id: string);
6
+ get(key: string): Promise<KindaPretty<KindaPretty<T>> | undefined>;
7
+ set(key: string, value: KindaPretty<T>, options?: {
8
+ onlyIfNotExists?: boolean;
9
+ }): Promise<void>;
10
+ delete(key: string): Promise<void>;
11
+ scan(): AsyncGenerator<{
12
+ key: string;
13
+ value: KindaPretty<T>;
14
+ }>;
15
+ isAlreadyExistsError(error: any): boolean;
16
+ }
@@ -0,0 +1,34 @@
1
+ import { Resource } from '../resource.js';
2
+ import { DistributedTable, PassThruParser } from './distributed-table.js';
3
+ import { overrides } from '../overrides.js';
4
+ const defineTable = (kvStore) => {
5
+ const ctor = overrides.DistributedTable || DistributedTable;
6
+ return new ctor(kvStore, 'table', {
7
+ parse: (PassThruParser),
8
+ key: { partition: { field: "key", type: 'string' } },
9
+ });
10
+ };
11
+ export class KeyValueStore extends Resource {
12
+ #table;
13
+ constructor(scope, id) {
14
+ super(scope, id);
15
+ this.#table = defineTable(this);
16
+ }
17
+ async get(key) {
18
+ return (await this.#table.get({ key }))?.value;
19
+ }
20
+ async set(key, value, options) {
21
+ return this.#table.save({ key, value }, { onlyIfNotExists: options?.onlyIfNotExists });
22
+ }
23
+ async delete(key) {
24
+ return this.#table.delete({ key });
25
+ }
26
+ async *scan() {
27
+ for await (const record of this.#table.scan({})) {
28
+ yield record;
29
+ }
30
+ }
31
+ isAlreadyExistsError(error) {
32
+ return this.#table.isAlreadyExistsError(error);
33
+ }
34
+ }
@@ -0,0 +1,25 @@
1
+ import { Resource } from '../resource.js';
2
+ type Initializer<ValidOptions extends string = string> = 'random' | (() => ValidOptions) | null | undefined;
3
+ export type SettingDetails<Init extends Initializer<ValidOptions>, ValidOptions extends string = string> = {
4
+ private: boolean;
5
+ description?: string;
6
+ init?: Init;
7
+ options?: ValidOptions[];
8
+ };
9
+ type ReturnTypeFor<T extends SettingDetails<Initializer, string>> = T extends SettingDetails<undefined | null, infer ValidOptions> ? (ValidOptions | undefined) : string;
10
+ type SetTypeFor<T extends SettingDetails<Initializer, string>> = T extends SettingDetails<undefined | null, infer ValidOptions> ? ValidOptions : string;
11
+ export declare class Setting<const Details extends SettingDetails<Initializer> = SettingDetails<null>> extends Resource {
12
+ private details;
13
+ constructor(scope: Resource | string, id: string, details?: Details);
14
+ get isPrivate(): boolean;
15
+ get description(): string | undefined;
16
+ get options(): string[] | undefined;
17
+ read(): Promise<ReturnTypeFor<Details>>;
18
+ write(value: SetTypeFor<Details>, options?: {
19
+ onlyIfNotExists?: boolean;
20
+ }): Promise<void | undefined>;
21
+ static list(): MapIterator<Setting<SettingDetails<Initializer<string>, string>>>;
22
+ static get(key: string): Setting<SettingDetails<Initializer<string>, string>> | undefined;
23
+ isAlreadyExistsError(error: any): boolean | undefined;
24
+ }
25
+ export {};
@@ -0,0 +1,61 @@
1
+ import * as crypto from 'crypto';
2
+ import { Resource } from '../resource.js';
3
+ import { KeyValueStore } from './key-value-store.js';
4
+ import { overrides } from '../overrides.js';
5
+ let settings;
6
+ const allSettings = new Map();
7
+ export class Setting extends Resource {
8
+ details;
9
+ constructor(scope, id, details) {
10
+ super(scope, id);
11
+ this.details = (details || { private: false });
12
+ settings = new (overrides.KeyValueStore || KeyValueStore)('wirejs', 'settings');
13
+ allSettings.set(this.absoluteId, this);
14
+ }
15
+ get isPrivate() {
16
+ return this.details.private;
17
+ }
18
+ get description() {
19
+ return this.details.description;
20
+ }
21
+ get options() {
22
+ return this.details.options;
23
+ }
24
+ async read() {
25
+ const existingValue = await settings?.get(this.absoluteId);
26
+ if (!existingValue && !!this.details.init) {
27
+ try {
28
+ const initValue = this.details.init === 'random'
29
+ ? crypto.randomBytes(64).toString('base64url')
30
+ : this.details.init();
31
+ await this.write(initValue, { onlyIfNotExists: true });
32
+ return initValue;
33
+ }
34
+ catch (error) {
35
+ if (!this.isAlreadyExistsError(error))
36
+ throw error;
37
+ return this.read();
38
+ }
39
+ }
40
+ else {
41
+ return existingValue?.value;
42
+ }
43
+ }
44
+ async write(value, options) {
45
+ return settings?.set(this.absoluteId, {
46
+ value,
47
+ private: this.details.private,
48
+ description: this.details.description,
49
+ options: this.details.options,
50
+ }, { onlyIfNotExists: options?.onlyIfNotExists });
51
+ }
52
+ static list() {
53
+ return allSettings.values();
54
+ }
55
+ static get(key) {
56
+ return allSettings.get(key);
57
+ }
58
+ isAlreadyExistsError(error) {
59
+ return settings?.isAlreadyExistsError(error);
60
+ }
61
+ }
@@ -0,0 +1,21 @@
1
+ import { Resource } from '../resource.js';
2
+ type SystemAttributeProperties = {
3
+ description: string;
4
+ value: string | null;
5
+ };
6
+ export declare class SystemAttribute extends Resource {
7
+ private props;
8
+ constructor(scope: Resource | string, id: string, props: SystemAttributeProperties);
9
+ get name(): string;
10
+ get description(): string;
11
+ get value(): string | null;
12
+ /**
13
+ * Lists attributes defined statically by code and modules within your
14
+ * codebase and imported modules. This is *not* intended to includes
15
+ * attributes defined by the build system or runtime. To enumerate all known
16
+ * attributes, use `Context.systemInfo`.
17
+ * @returns
18
+ */
19
+ static listStaticAttributes(): MapIterator<SystemAttribute>;
20
+ }
21
+ export {};
@@ -0,0 +1,29 @@
1
+ import { Resource } from '../resource.js';
2
+ const staticAttributes = new Map();
3
+ export class SystemAttribute extends Resource {
4
+ props;
5
+ constructor(scope, id, props) {
6
+ super(scope, id);
7
+ this.props = props;
8
+ staticAttributes.set(this.absoluteId, this);
9
+ }
10
+ get name() {
11
+ return this.absoluteId;
12
+ }
13
+ get description() {
14
+ return this.props.description;
15
+ }
16
+ get value() {
17
+ return this.props.value;
18
+ }
19
+ /**
20
+ * Lists attributes defined statically by code and modules within your
21
+ * codebase and imported modules. This is *not* intended to includes
22
+ * attributes defined by the build system or runtime. To enumerate all known
23
+ * attributes, use `Context.systemInfo`.
24
+ * @returns
25
+ */
26
+ static listStaticAttributes() {
27
+ return staticAttributes.values();
28
+ }
29
+ }
@@ -1,7 +1,7 @@
1
1
  import { scrypt, randomBytes, randomUUID } from 'crypto';
2
2
  import { Resource } from '../resource.js';
3
3
  import { FileService } from './file.js';
4
- import { Secret } from '../resources/secret.js';
4
+ import { Setting } from '../resources/setting.js';
5
5
  import { SignedCookie } from '../adapters/signed-cookie.js';
6
6
  import { withContext } from '../adapters/context.js';
7
7
  import { overrides } from '../overrides.js';
@@ -187,7 +187,11 @@ export class AuthenticationService extends Resource {
187
187
  constructor(scope, id, { duration, keepalive, cookie } = {}) {
188
188
  super(scope, id);
189
189
  this.#keepalive = !!keepalive;
190
- const signingSecret = new (overrides.Secret || Secret)(this, 'jwt-signing-secret');
190
+ const SettingCtor = overrides.Setting || Setting;
191
+ const signingSecret = new SettingCtor(this, 'jwt-signing-secret', {
192
+ private: true, init: 'random',
193
+ description: "Used for signing the auth JWT. Should generally not be changed."
194
+ });
191
195
  const fileService = new (overrides.FileService || FileService)(this, 'files');
192
196
  this.#cookie = new SignedCookie(this, cookie ?? 'identity', signingSecret,
193
197
  // in the future, we might want to set `secure` based on whether we're in
@@ -28,11 +28,19 @@ export class FileService extends Resource {
28
28
  async *list({ prefix = '' } = {}) {
29
29
  const root = this.#fullNameFor('');
30
30
  try {
31
- const all = await fs.promises.readdir(root, { recursive: true });
31
+ const entries = await fs.promises.readdir(root, {
32
+ recursive: true,
33
+ withFileTypes: true
34
+ });
35
+ const all = entries
36
+ .filter(entry => entry.isFile())
37
+ .map(entry => path.join(entry.parentPath, entry.name));
32
38
  for (const name of all) {
39
+ const relativeName = name.slice(root.length);
40
+ console.log('relativeName', relativeName);
33
41
  if (prefix === undefined
34
- || name.slice(0, root.length).startsWith(prefix)) {
35
- yield name;
42
+ || relativeName.startsWith(prefix)) {
43
+ yield relativeName;
36
44
  }
37
45
  }
38
46
  }
@@ -1,4 +1,5 @@
1
1
  import { Resource } from '../resource.js';
2
+ import { Context } from '../adapters/context.js';
2
3
  export type MessageStream<T = any> = {
3
4
  /**
4
5
  * Returns a function to close the subscription.
@@ -16,7 +17,7 @@ export declare class RealtimeService<T = any> extends Resource {
16
17
  /**
17
18
  * The address the client will need to connect to.
18
19
  */
19
- get address(): string;
20
+ address(context: Context): string;
20
21
  publish(channel: string, events: T[]): Promise<void>;
21
- getStream(channel: string): Promise<MessageStream<T>>;
22
+ getStream(context: Context, channel: string): Promise<MessageStream<T>>;
22
23
  }
@@ -1,7 +1,7 @@
1
1
  import * as jose from 'jose';
2
2
  import { WebSocketServer, WebSocket } from 'ws';
3
3
  import { Resource } from '../resource.js';
4
- import { Secret } from '../resources/secret.js';
4
+ import { Setting } from '../resources/setting.js';
5
5
  import { overrides } from '../overrides.js';
6
6
  const servers = new Map();
7
7
  const channels = new Map();
@@ -11,7 +11,10 @@ export class RealtimeService extends Resource {
11
11
  #server;
12
12
  constructor(scope, id) {
13
13
  super(scope, id);
14
- this.#secret = new (overrides.Secret || Secret)(this, 'auth-secret');
14
+ this.#secret = new (overrides.Setting || Setting)(this, 'auth-secret', {
15
+ private: true, init: 'random',
16
+ description: "Used to sign realtime subscription authorization. Should generally not be changed."
17
+ });
15
18
  if (servers.has(this.absoluteId)) {
16
19
  console.log('existing realtime service found');
17
20
  this.#server = servers.get(this.absoluteId);
@@ -29,7 +32,7 @@ export class RealtimeService extends Resource {
29
32
  if (this.#server)
30
33
  return;
31
34
  console.log('Starting WebSocket server...');
32
- this.#server = new WebSocketServer({ port: 0 });
35
+ this.#server = new WebSocketServer({ port: 3001 });
33
36
  servers.set(this.absoluteId, this.#server);
34
37
  this.#server.on('connection', async (ws, req) => {
35
38
  const encodedChannel = req.url?.split('/')[1];
@@ -97,7 +100,7 @@ export class RealtimeService extends Resource {
97
100
  /**
98
101
  * The address the client will need to connect to.
99
102
  */
100
- get address() {
103
+ address(context) {
101
104
  this.#requireServer();
102
105
  const address = this.#server.address();
103
106
  if (typeof address === 'string') {
@@ -105,7 +108,7 @@ export class RealtimeService extends Resource {
105
108
  }
106
109
  else if (typeof address === 'object' && address !== null) {
107
110
  const { port } = address;
108
- return `ws://localhost:${port}`;
111
+ return `ws://${context.location.hostname}:${port}`;
109
112
  }
110
113
  else {
111
114
  throw new Error('Server address is not available');
@@ -123,7 +126,7 @@ export class RealtimeService extends Resource {
123
126
  }
124
127
  });
125
128
  }
126
- async getStream(channel) {
129
+ async getStream(context, channel) {
127
130
  this.#requireServer();
128
131
  this.#validateChannelName(channel);
129
132
  const channelString = Buffer.from(channel).toString('base64');
@@ -137,7 +140,7 @@ export class RealtimeService extends Resource {
137
140
  // It's the metadata needed to satisfy the contract client-side.
138
141
  return {
139
142
  __wjstype: 'realtime',
140
- url: `${this.address}/${channelString}`,
143
+ url: `${this.address(context)}/${channelString}`,
141
144
  protocol: jwt
142
145
  };
143
146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirejs-resources",
3
- "version": "0.1.105",
3
+ "version": "0.1.107-payments",
4
4
  "description": "Basic services and server-side resources for wirejs apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- export declare function apiTree(INTERNAL_API_URL: string, path?: string[]): () => void;
@@ -1,68 +0,0 @@
1
- async function callApi(INTERNAL_API_URL, method, ...args) {
2
- function isNode() {
3
- return typeof args[0]?.cookies?.getAll === 'function';
4
- }
5
- function apiUrl() {
6
- if (isNode()) {
7
- return INTERNAL_API_URL;
8
- }
9
- else {
10
- return "/api";
11
- }
12
- }
13
- let cookieHeader = {};
14
- if (isNode()) {
15
- const context = args[0];
16
- const cookies = context.cookies.getAll();
17
- cookieHeader = typeof cookies === 'object'
18
- ? {
19
- Cookie: Object.entries(cookies).map(kv => kv.join('=')).join('; ')
20
- }
21
- : {};
22
- }
23
- const response = await fetch(apiUrl(), {
24
- method: 'POST',
25
- headers: {
26
- 'Content-Type': 'application/json',
27
- ...cookieHeader
28
- },
29
- body: JSON.stringify([{ method, args: [...args] }]),
30
- });
31
- const body = await response.json();
32
- if (isNode()) {
33
- const context = args[0];
34
- for (const c of response.headers.getSetCookie()) {
35
- const parts = c.split(';').map(p => p.trim());
36
- const flags = parts.slice(1);
37
- const [name, value] = parts[0].split('=').map(decodeURIComponent);
38
- const httpOnly = flags.includes('HttpOnly');
39
- const secure = flags.includes('Secure');
40
- const maxAgePart = flags.find(f => f.startsWith('Max-Age='))?.split('=')[1];
41
- context.cookies.set({
42
- name,
43
- value,
44
- httpOnly,
45
- secure,
46
- maxAge: maxAgePart ? parseInt(maxAgePart) : undefined
47
- });
48
- }
49
- }
50
- const error = body[0].error;
51
- if (error) {
52
- throw new Error(error);
53
- }
54
- const value = body[0].data;
55
- return value;
56
- }
57
- ;
58
- export function apiTree(INTERNAL_API_URL, path = []) {
59
- return new Proxy(function () { }, {
60
- apply(_target, _thisArg, args) {
61
- return callApi(INTERNAL_API_URL, path, ...args);
62
- },
63
- get(_target, prop) {
64
- return apiTree(INTERNAL_API_URL, [...path, prop]);
65
- }
66
- });
67
- }
68
- ;
@@ -1 +0,0 @@
1
- export declare function apiTree(INTERNAL_API_URL: string, path?: string[]): () => void;
@@ -1,68 +0,0 @@
1
- async function callApi(INTERNAL_API_URL, method, ...args) {
2
- function isNode() {
3
- return typeof args[0]?.cookies?.getAll === 'function';
4
- }
5
- function apiUrl() {
6
- if (isNode()) {
7
- return INTERNAL_API_URL;
8
- }
9
- else {
10
- return "/api";
11
- }
12
- }
13
- let cookieHeader = {};
14
- if (isNode()) {
15
- const context = args[0];
16
- const cookies = context.cookies.getAll();
17
- cookieHeader = typeof cookies === 'object'
18
- ? {
19
- Cookie: Object.entries(cookies).map(kv => kv.join('=')).join('; ')
20
- }
21
- : {};
22
- }
23
- const response = await fetch(apiUrl(), {
24
- method: 'POST',
25
- headers: {
26
- 'Content-Type': 'application/json',
27
- ...cookieHeader
28
- },
29
- body: JSON.stringify([{ method, args: [...args] }]),
30
- });
31
- const body = await response.json();
32
- if (isNode()) {
33
- const context = args[0];
34
- for (const c of response.headers.getSetCookie()) {
35
- const parts = c.split(';').map(p => p.trim());
36
- const flags = parts.slice(1);
37
- const [name, value] = parts[0].split('=').map(decodeURIComponent);
38
- const httpOnly = flags.includes('HttpOnly');
39
- const secure = flags.includes('Secure');
40
- const maxAgePart = flags.find(f => f.startsWith('Max-Age='))?.split('=')[1];
41
- context.cookies.set({
42
- name,
43
- value,
44
- httpOnly,
45
- secure,
46
- maxAge: maxAgePart ? parseInt(maxAgePart) : undefined
47
- });
48
- }
49
- }
50
- const error = body[0].error;
51
- if (error) {
52
- throw new Error(error);
53
- }
54
- const value = body[0].data;
55
- return value;
56
- }
57
- ;
58
- export function apiTree(INTERNAL_API_URL, path = []) {
59
- return new Proxy(function () { }, {
60
- apply(_target, _thisArg, args) {
61
- return callApi(INTERNAL_API_URL, path, ...args);
62
- },
63
- get(_target, prop) {
64
- return apiTree(INTERNAL_API_URL, [...path, prop]);
65
- }
66
- });
67
- }
68
- ;
@@ -1 +0,0 @@
1
- export declare function prebuildApi(): Promise<void>;
@@ -1,27 +0,0 @@
1
- import process from 'process';
2
- import fs from 'fs';
3
- import path from 'path';
4
- export async function prebuildApi() {
5
- const CWD = process.cwd();
6
- let API_URL = '/api';
7
- const indexModule = await import(path.join(CWD, 'index.js'));
8
- try {
9
- const backendConfigModule = await import(path.join(CWD, 'config.js'));
10
- const backendConfig = backendConfigModule.default;
11
- console.log("backend config found", backendConfig);
12
- if (backendConfig.apiUrl) {
13
- API_URL = backendConfig.apiUrl;
14
- }
15
- }
16
- catch {
17
- console.log("No backend API config found.");
18
- }
19
- const apiCode = Object.keys(indexModule)
20
- .map(k => `export const ${k} = apiTree(INTERNAL_API_URL, ${JSON.stringify([k])});`)
21
- .join('\n');
22
- const baseClient = [
23
- `import { apiTree } from "wirejs-resources/hosting/client.js";`,
24
- `const INTERNAL_API_URL = ${JSON.stringify(API_URL)};`,
25
- ].join('\n');
26
- await fs.promises.writeFile(path.join(CWD, 'index.client.js'), [baseClient, apiCode].join('\n\n'));
27
- }