mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
WIP
This commit is contained in:
parent
e647b0057a
commit
b795c6184e
@ -4,6 +4,13 @@ const httpz = @import("httpz");
|
|||||||
|
|
||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
|
pub const Env = struct {
|
||||||
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
|
logger: jetzig.loggers.Logger,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn RoutedChannel(Routes: type) type {
|
pub fn RoutedChannel(Routes: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Channel = @This();
|
const Channel = @This();
|
||||||
@ -12,6 +19,32 @@ pub fn RoutedChannel(Routes: type) type {
|
|||||||
websocket: *jetzig.websockets.RoutedWebsocket(Routes),
|
websocket: *jetzig.websockets.RoutedWebsocket(Routes),
|
||||||
state: *jetzig.data.Value,
|
state: *jetzig.data.Value,
|
||||||
data: *jetzig.data.Data,
|
data: *jetzig.data.Data,
|
||||||
|
_connections: *std.StringHashMap(Connection),
|
||||||
|
env: Env,
|
||||||
|
|
||||||
|
const Connection = struct {
|
||||||
|
object: *jetzig.data.Value,
|
||||||
|
data: *jetzig.Data,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, websocket: *jetzig.websockets.Websocket) !Channel {
|
||||||
|
const connections = try allocator.create(std.StringHashMap(Connection));
|
||||||
|
connections.* = std.StringHashMap(Connection).init(allocator);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.websocket = websocket,
|
||||||
|
.state = try websocket.getState(),
|
||||||
|
.data = websocket.data,
|
||||||
|
._connections = connections,
|
||||||
|
.env = .{
|
||||||
|
.store = websocket.store,
|
||||||
|
.cache = websocket.cache,
|
||||||
|
.job_queue = websocket.job_queue,
|
||||||
|
.logger = websocket.logger,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn publish(channel: Channel, data: anytype) !void {
|
pub fn publish(channel: Channel, data: anytype) !void {
|
||||||
var stack_fallback = std.heap.stackFallback(4096, channel.allocator);
|
var stack_fallback = std.heap.stackFallback(4096, channel.allocator);
|
||||||
@ -23,7 +56,7 @@ pub fn RoutedChannel(Routes: type) type {
|
|||||||
const writer = write_buffer.writer();
|
const writer = write_buffer.writer();
|
||||||
try std.json.stringify(data, .{}, writer);
|
try std.json.stringify(data, .{}, writer);
|
||||||
try write_buffer.flush();
|
try write_buffer.flush();
|
||||||
channel.websocket.logger.DEBUG(
|
channel.env.logger.DEBUG(
|
||||||
"Published Channel message for `{s}`",
|
"Published Channel message for `{s}`",
|
||||||
.{channel.websocket.route.path},
|
.{channel.websocket.route.path},
|
||||||
) catch {};
|
) catch {};
|
||||||
@ -45,12 +78,48 @@ pub fn RoutedChannel(Routes: type) type {
|
|||||||
try writer.writeAll("__jetzig_event__:");
|
try writer.writeAll("__jetzig_event__:");
|
||||||
try std.json.stringify(.{ .method = method, .params = args }, .{}, writer);
|
try std.json.stringify(.{ .method = method, .params = args }, .{}, writer);
|
||||||
try write_buffer.flush();
|
try write_buffer.flush();
|
||||||
channel.websocket.logger.DEBUG(
|
channel.env.logger.DEBUG(
|
||||||
"Invoked Javascript function `{s}` for `{s}`",
|
"Invoked Javascript function `{s}` for `{s}`",
|
||||||
.{ @tagName(method), channel.websocket.route.path },
|
.{ @tagName(method), channel.websocket.route.path },
|
||||||
) catch {};
|
) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connect(channel: Channel, comptime scope: []const u8) !*jetzig.data.Value {
|
||||||
|
if (channel._connections.get(scope)) |cached| {
|
||||||
|
return cached.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.websocket.session_id.len != 32) return error.JetzigInvalidSessionIdLength;
|
||||||
|
|
||||||
|
const connections = channel.get("_connections") orelse try channel.put("_connections", .array);
|
||||||
|
const connection_id = for (connections.items(.array)) |connection| {
|
||||||
|
if (connection.getT(.string, "scope")) |connection_scope| {
|
||||||
|
if (std.mem.eql(u8, connection_scope, scope)) {
|
||||||
|
break connection.getT(.string, "id") orelse return error.JetzigInvalidChannelState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else blk: {
|
||||||
|
const id = try channel.allocator.alloc(u8, 32);
|
||||||
|
_ = jetzig.util.generateRandomString(id);
|
||||||
|
try connections.append(.{ .id = id, .scope = scope });
|
||||||
|
break :blk id;
|
||||||
|
};
|
||||||
|
|
||||||
|
var buf: [32 + ":".len + 32]u8 = undefined;
|
||||||
|
const connection_key = try std.fmt.bufPrint(&buf, "{s}:{s}", .{ channel.websocket.session_id, connection_id });
|
||||||
|
return try channel.websocket.channels.get(channel.data, connection_key) orelse blk: {
|
||||||
|
const data = try channel.allocator.create(jetzig.Data);
|
||||||
|
data.* = jetzig.Data.init(channel.allocator);
|
||||||
|
const object = try data.root(.object);
|
||||||
|
const duped_connection_key = try channel.allocator.dupe(u8, connection_key);
|
||||||
|
try channel.websocket.channels.put(duped_connection_key, object);
|
||||||
|
try channel._connections.put(scope, .{ .data = data, .object = object });
|
||||||
|
const connections_state = channel.get("_connections_state") orelse try channel.put("_connections_state", .object);
|
||||||
|
try connections_state.put(scope, object);
|
||||||
|
break :blk object;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getT(
|
pub fn getT(
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
comptime T: jetzig.data.Data.ValueType,
|
comptime T: jetzig.data.Data.ValueType,
|
||||||
@ -76,7 +145,13 @@ pub fn RoutedChannel(Routes: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync(channel: Channel) !void {
|
pub fn sync(channel: Channel) !void {
|
||||||
try channel.websocket.syncState(channel);
|
try channel.websocket.syncState(channel.data, "__root__");
|
||||||
|
var it = channel._connections.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const data = entry.value_ptr.*.data;
|
||||||
|
const scope = entry.key_ptr.*;
|
||||||
|
try channel.websocket.syncState(data, scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -225,6 +225,9 @@ pub fn RoutedServer(Routes: type) type {
|
|||||||
.route = route,
|
.route = route,
|
||||||
.session_id = session_id,
|
.session_id = session_id,
|
||||||
.channels = self.channels,
|
.channels = self.channels,
|
||||||
|
.store = self.store,
|
||||||
|
.cache = self.cache,
|
||||||
|
.job_queue = self.job_queue,
|
||||||
.logger = self.logger,
|
.logger = self.logger,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -2,175 +2,189 @@ window.jetzig = window.jetzig ? window.jetzig : {}
|
|||||||
jetzig = window.jetzig;
|
jetzig = window.jetzig;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
const state_tag = "__jetzig_channel_state__:";
|
||||||
|
const actions_tag = "__jetzig_actions__:";
|
||||||
|
const event_tag = "__jetzig_event__:";
|
||||||
|
|
||||||
const transform = (value, state, element) => {
|
const transform = (value, state, element) => {
|
||||||
const id = element.getAttribute('jetzig-id');
|
const id = element.getAttribute('jetzig-id');
|
||||||
const transformer = id && jetzig.channel.transformers[id];
|
const transformer = id && jetzig.channel.transformers[id];
|
||||||
if (transformer) {
|
if (transformer) {
|
||||||
return transformer(value, state, element);
|
return transformer(value, state, element);
|
||||||
|
} else {
|
||||||
|
return value === undefined || value == null ? '' : `${value}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reduceState = (ref, state) => {
|
||||||
|
if (!ref.startsWith('$.')) throw new Error(`Unexpected ref format: ${ref}`);
|
||||||
|
const args = ref.split('.');
|
||||||
|
args.shift();
|
||||||
|
const isNumeric = (string) => [...string].every(char => '0123456789'.includes(char));
|
||||||
|
const isObject = (object) => object && typeof object === 'object';
|
||||||
|
return args.reduce((acc, arg) => {
|
||||||
|
if (isNumeric(arg)) {
|
||||||
|
if (acc && Array.isArray(acc) && acc.length > arg) return acc[parseInt(arg)];
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return value === undefined || value == null ? '' : `${value}`
|
if (acc && isObject(acc)) return acc[arg];
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
}, state);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleState = (event, channel) => {
|
||||||
|
// TODO: Handle different scopes and update elements based on scope.
|
||||||
|
const detagged = event.data.slice(state_tag.length);
|
||||||
|
const scope = detagged.split(':', 1)[0];
|
||||||
|
const state = JSON.parse(detagged.slice(scope.length + 1));
|
||||||
|
Object.entries(channel.elementMap).forEach(([ref, elements]) => {
|
||||||
|
const value = reduceState(ref, state);
|
||||||
|
elements.forEach(element => element.innerHTML = transform(value, state, element));
|
||||||
|
});
|
||||||
|
channel.stateChangedCallbacks.forEach((callback) => {
|
||||||
|
callback(state);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEvent = (event, channel) => {
|
||||||
|
const data = JSON.parse(event.data.slice(event_tag.length));
|
||||||
|
if (Object.hasOwn(channel.invokeCallbacks, data.method)) {
|
||||||
|
channel.invokeCallbacks[data.method].forEach(callback => {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAction = (event, channel) => {
|
||||||
|
const data = JSON.parse(event.data.slice(actions_tag.length));
|
||||||
|
data.actions.forEach(action => {
|
||||||
|
channel.action_specs[action.name] = {
|
||||||
|
callback: (...params) => {
|
||||||
|
if (action.params.length != params.length) {
|
||||||
|
throw new Error(`Invalid params for action '${action.name}'. Expected ${action.params.length} params, found ${params.length}`);
|
||||||
|
}
|
||||||
|
[...action.params].forEach((param, index) => {
|
||||||
|
if (param.type !== typeof params[index]) {
|
||||||
|
const err = `Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${param.type}, found ${typeof params[index]}`;
|
||||||
|
switch (param.type) {
|
||||||
|
case "string":
|
||||||
|
params[index] = `${params[index]}`;
|
||||||
|
break;
|
||||||
|
case "integer":
|
||||||
|
try { params[index] = parseInt(params[index]) } catch {
|
||||||
|
throw new Error(err);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "float":
|
||||||
|
try { params[index] = parseFloat(params[index]) } catch {
|
||||||
|
throw new Error(err);
|
||||||
|
};
|
||||||
|
case "boolean":
|
||||||
|
params[index] = ["true", "y", "1", "yes", "t"].includes(params[index]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
||||||
|
},
|
||||||
|
spec: { ...action },
|
||||||
|
};
|
||||||
|
channel.actions[action.name] = channel.action_specs[action.name].callback;
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[jetzig-click]').forEach(element => {
|
||||||
|
const ref = element.getAttribute('jetzig-click');
|
||||||
|
const action = channel.action_specs[ref];
|
||||||
|
if (action) {
|
||||||
|
element.addEventListener('click', () => {
|
||||||
|
const args = [];
|
||||||
|
action.spec.params.forEach(param => {
|
||||||
|
const arg = element.dataset[param.name];
|
||||||
|
if (arg === undefined) {
|
||||||
|
throw new Error(`Expected 'data-${param.name}' attribute for '${action.name}' click handler.`);
|
||||||
|
} else {
|
||||||
|
args.push(element.dataset[param.name]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
action.callback(...args);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown click handler: '${ref}'`);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
jetzig.channel = {
|
jetzig.channel = {
|
||||||
websocket: null,
|
websocket: null,
|
||||||
actions: {},
|
actions: {},
|
||||||
action_specs: {},
|
action_specs: {},
|
||||||
stateChangedCallbacks: [],
|
stateChangedCallbacks: [],
|
||||||
messageCallbacks: [],
|
messageCallbacks: [],
|
||||||
invokeCallbacks: {},
|
invokeCallbacks: {},
|
||||||
elementMap: {},
|
elementMap: {},
|
||||||
transformers: {},
|
transformers: {},
|
||||||
onStateChanged: function(callback) { this.stateChangedCallbacks.push(callback); },
|
onStateChanged: function(callback) { this.stateChangedCallbacks.push(callback); },
|
||||||
onMessage: function(callback) { this.messageCallbacks.push(callback); },
|
onMessage: function(callback) { this.messageCallbacks.push(callback); },
|
||||||
init: function(host, path) {
|
init: function(host, path) {
|
||||||
this.websocket = new WebSocket(`ws://${host}${path}`);
|
this.websocket = new WebSocket(`ws://${host}${path}`);
|
||||||
this.websocket.addEventListener("message", (event) => {
|
this.websocket.addEventListener("message", (event) => {
|
||||||
const state_tag = "__jetzig_channel_state__:";
|
if (event.data.startsWith(state_tag)) {
|
||||||
const actions_tag = "__jetzig_actions__:";
|
handleState(event, this);
|
||||||
const event_tag = "__jetzig_event__:";
|
} else if (event.data.startsWith(event_tag)) {
|
||||||
|
handleEvent(event, this);
|
||||||
if (event.data.startsWith(state_tag)) {
|
} else if (event.data.startsWith(actions_tag)) {
|
||||||
const state = JSON.parse(event.data.slice(state_tag.length));
|
handleAction(event, this);
|
||||||
Object.entries(this.elementMap).forEach(([ref, elements]) => {
|
} else {
|
||||||
const value = reduceState(ref, state);
|
const data = JSON.parse(event.data);
|
||||||
elements.forEach(element => element.innerHTML = transform(value, state, element));
|
this.messageCallbacks.forEach((callback) => {
|
||||||
});
|
callback(data);
|
||||||
this.stateChangedCallbacks.forEach((callback) => {
|
|
||||||
callback(state);
|
|
||||||
});
|
|
||||||
} else if (event.data.startsWith(event_tag)) {
|
|
||||||
const data = JSON.parse(event.data.slice(event_tag.length));
|
|
||||||
if (Object.hasOwn(this.invokeCallbacks, data.method)) {
|
|
||||||
this.invokeCallbacks[data.method].forEach(callback => {
|
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (event.data.startsWith(actions_tag)) {
|
|
||||||
const data = JSON.parse(event.data.slice(actions_tag.length));
|
|
||||||
data.actions.forEach(action => {
|
|
||||||
this.action_specs[action.name] = {
|
|
||||||
callback: (...params) => {
|
|
||||||
if (action.params.length != params.length) {
|
|
||||||
throw new Error(`Invalid params for action '${action.name}'. Expected ${action.params.length} params, found ${params.length}`);
|
|
||||||
}
|
|
||||||
[...action.params].forEach((param, index) => {
|
|
||||||
if (param.type !== typeof params[index]) {
|
|
||||||
const err = `Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${param.type}, found ${typeof params[index]}`;
|
|
||||||
switch (param.type) {
|
|
||||||
case "string":
|
|
||||||
params[index] = `${params[index]}`;
|
|
||||||
break;
|
|
||||||
case "integer":
|
|
||||||
try { params[index] = parseInt(params[index]) } catch {
|
|
||||||
throw new Error(err);
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "float":
|
|
||||||
try { params[index] = parseFloat(params[index]) } catch {
|
|
||||||
throw new Error(err);
|
|
||||||
};
|
|
||||||
case "boolean":
|
|
||||||
params[index] = ["true", "y", "1", "yes", "t"].includes(params[index]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
|
||||||
},
|
|
||||||
spec: { ...action },
|
|
||||||
};
|
|
||||||
this.actions[action.name] = this.action_specs[action.name].callback;
|
|
||||||
});
|
|
||||||
document.querySelectorAll('[jetzig-click]').forEach(element => {
|
|
||||||
const ref = element.getAttribute('jetzig-click');
|
|
||||||
const action = this.action_specs[ref];
|
|
||||||
if (action) {
|
|
||||||
element.addEventListener('click', () => {
|
|
||||||
const args = [];
|
|
||||||
action.spec.params.forEach(param => {
|
|
||||||
const arg = element.dataset[param.name];
|
|
||||||
if (arg === undefined) {
|
|
||||||
throw new Error(`Expected 'data-${param.name}' attribute for '${action.name}' click handler.`);
|
|
||||||
} else {
|
|
||||||
args.push(element.dataset[param.name]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
action.callback(...args);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown click handler: '${ref}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
this.messageCallbacks.forEach((callback) => {
|
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const reduceState = (ref, state) => {
|
document.querySelectorAll('[jetzig-connect]').forEach(element => {
|
||||||
if (!ref.startsWith('$.')) throw new Error(`Unexpected ref format: ${ref}`);
|
const ref = element.getAttribute('jetzig-connect');
|
||||||
const args = ref.split('.');
|
if (!this.elementMap[ref]) this.elementMap[ref] = [];
|
||||||
args.shift();
|
const id = `jetzig-${crypto.randomUUID()}`;
|
||||||
const isNumeric = (string) => [...string].every(char => '0123456789'.includes(char));
|
element.setAttribute('jetzig-id', id);
|
||||||
const isObject = (object) => object && typeof object === 'object';
|
this.elementMap[ref].push(element);
|
||||||
return args.reduce((acc, arg) => {
|
const transformer = element.getAttribute('jetzig-transform');
|
||||||
if (isNumeric(arg)) {
|
if (transformer) {
|
||||||
if (acc && Array.isArray(acc) && acc.length > arg) return acc[parseInt(arg)];
|
this.transformers[id] = new Function("value", "$", "element", `return ${transformer};`);
|
||||||
return null;
|
}
|
||||||
} else {
|
});
|
||||||
if (acc && isObject(acc)) return acc[arg];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, state);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.querySelectorAll('[jetzig-connect]').forEach(element => {
|
const styled_elements = document.querySelectorAll('[jetzig-style]');
|
||||||
const ref = element.getAttribute('jetzig-connect');
|
this.onStateChanged(state => {
|
||||||
if (!this.elementMap[ref]) this.elementMap[ref] = [];
|
styled_elements.forEach(element => {
|
||||||
const id = `jetzig-${crypto.randomUUID()}`;
|
const func = new Function("$", `return ${element.getAttribute('jetzig-style')};`)
|
||||||
element.setAttribute('jetzig-id', id);
|
const styles = func(state);
|
||||||
this.elementMap[ref].push(element);
|
Object.entries(styles).forEach(([key, value]) => {
|
||||||
const transformer = element.getAttribute('jetzig-transform');
|
element.style.setProperty(key, value);
|
||||||
if (transformer) {
|
|
||||||
this.transformers[id] = new Function("value", "$", "element", `return ${transformer};`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const styled_elements = document.querySelectorAll('[jetzig-style]');
|
// this.websocket.addEventListener("open", (event) => {
|
||||||
this.onStateChanged(state => {
|
// // TODO
|
||||||
styled_elements.forEach(element => {
|
// this.publish("websockets", {});
|
||||||
const func = new Function("$", `return ${element.getAttribute('jetzig-style')};`)
|
// });
|
||||||
const styles = func(state);
|
},
|
||||||
Object.entries(styles).forEach(([key, value]) => {
|
receive: function(ref, callback) {
|
||||||
element.style.setProperty(key, value);
|
if (Object.hasOwn(this.invokeCallbacks, ref)) {
|
||||||
});
|
this.invokeCallbacks[ref].push(callback);
|
||||||
});
|
} else {
|
||||||
});
|
this.invokeCallbacks[ref] = [callback];
|
||||||
|
}
|
||||||
// this.websocket.addEventListener("open", (event) => {
|
},
|
||||||
// // TODO
|
publish: function(data) {
|
||||||
// this.publish("websockets", {});
|
if (this.websocket) {
|
||||||
// });
|
const json = JSON.stringify(data);
|
||||||
},
|
this.websocket.send(json);
|
||||||
receive: function(ref, callback) {
|
}
|
||||||
if (Object.hasOwn(this.invokeCallbacks, ref)) {
|
},
|
||||||
this.invokeCallbacks[ref].push(callback);
|
|
||||||
} else {
|
|
||||||
this.invokeCallbacks[ref] = [callback];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
publish: function(data) {
|
|
||||||
if (this.websocket) {
|
|
||||||
const json = JSON.stringify(data);
|
|
||||||
this.websocket.send(json);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
@ -9,6 +9,9 @@ pub const Context = struct {
|
|||||||
route: jetzig.channels.Route,
|
route: jetzig.channels.Route,
|
||||||
session_id: []const u8,
|
session_id: []const u8,
|
||||||
channels: *jetzig.kv.Store.ChannelStore,
|
channels: *jetzig.kv.Store.ChannelStore,
|
||||||
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,6 +20,9 @@ pub fn RoutedWebsocket(Routes: type) type {
|
|||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
connection: *httpz.websocket.Conn,
|
connection: *httpz.websocket.Conn,
|
||||||
channels: *jetzig.kv.Store.ChannelStore,
|
channels: *jetzig.kv.Store.ChannelStore,
|
||||||
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
route: jetzig.channels.Route,
|
route: jetzig.channels.Route,
|
||||||
data: *jetzig.Data,
|
data: *jetzig.Data,
|
||||||
session_id: []const u8,
|
session_id: []const u8,
|
||||||
@ -35,6 +41,9 @@ pub fn RoutedWebsocket(Routes: type) type {
|
|||||||
.route = context.route,
|
.route = context.route,
|
||||||
.session_id = context.session_id,
|
.session_id = context.session_id,
|
||||||
.channels = context.channels,
|
.channels = context.channels,
|
||||||
|
.store = context.store,
|
||||||
|
.cache = context.cache,
|
||||||
|
.job_queue = context.job_queue,
|
||||||
.logger = context.logger,
|
.logger = context.logger,
|
||||||
.data = data,
|
.data = data,
|
||||||
};
|
};
|
||||||
@ -56,22 +65,12 @@ pub fn RoutedWebsocket(Routes: type) type {
|
|||||||
|
|
||||||
const func = websocket.route.openConnectionFn orelse return;
|
const func = websocket.route.openConnectionFn orelse return;
|
||||||
|
|
||||||
const channel = jetzig.channels.Channel{
|
const channel = try jetzig.channels.RoutedChannel(Routes).init(websocket.allocator, websocket);
|
||||||
.allocator = websocket.allocator,
|
|
||||||
.websocket = websocket,
|
|
||||||
.state = try websocket.getState(),
|
|
||||||
.data = websocket.data,
|
|
||||||
};
|
|
||||||
try func(channel);
|
try func(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clientMessage(websocket: *Websocket, allocator: std.mem.Allocator, data: []const u8) !void {
|
pub fn clientMessage(websocket: *Websocket, allocator: std.mem.Allocator, data: []const u8) !void {
|
||||||
const channel = jetzig.channels.RoutedChannel(Routes){
|
const channel = try jetzig.channels.RoutedChannel(Routes).init(allocator, websocket);
|
||||||
.allocator = allocator,
|
|
||||||
.websocket = websocket,
|
|
||||||
.state = try websocket.getState(),
|
|
||||||
.data = websocket.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (websocket.invoke(channel, data)) |maybe_action| {
|
if (websocket.invoke(channel, data)) |maybe_action| {
|
||||||
if (maybe_action) |action| {
|
if (maybe_action) |action| {
|
||||||
@ -94,18 +93,20 @@ pub fn RoutedWebsocket(Routes: type) type {
|
|||||||
websocket.logger.DEBUG("Routed Channel message for `{s}`", .{websocket.route.path}) catch {};
|
websocket.logger.DEBUG("Routed Channel message for `{s}`", .{websocket.route.path}) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn syncState(websocket: *Websocket, channel: jetzig.channels.RoutedChannel(Routes)) !void {
|
pub fn syncState(websocket: *Websocket, data: *jetzig.Data, scope: []const u8) !void {
|
||||||
var stack_fallback = std.heap.stackFallback(4096, channel.allocator);
|
const value = data.value orelse return;
|
||||||
|
|
||||||
|
var stack_fallback = std.heap.stackFallback(4096, websocket.allocator);
|
||||||
const allocator = stack_fallback.get();
|
const allocator = stack_fallback.get();
|
||||||
|
|
||||||
var write_buffer = channel.websocket.connection.writeBuffer(allocator, .text);
|
var write_buffer = websocket.connection.writeBuffer(allocator, .text);
|
||||||
defer write_buffer.deinit();
|
defer write_buffer.deinit();
|
||||||
|
|
||||||
const writer = write_buffer.writer();
|
const writer = write_buffer.writer();
|
||||||
|
|
||||||
// TODO: Make this really fast.
|
// TODO: Make this really fast.
|
||||||
try websocket.channels.put(websocket.session_id, channel.state);
|
try websocket.channels.put(websocket.session_id, value);
|
||||||
try writer.print("__jetzig_channel_state__:{s}", .{try websocket.data.toJson()});
|
try writer.print("__jetzig_channel_state__:{s}:{s}", .{ scope, try data.toJson() });
|
||||||
try write_buffer.flush();
|
try write_buffer.flush();
|
||||||
|
|
||||||
websocket.logger.DEBUG("Synchronized Channel state for `{s}`", .{websocket.route.path}) catch {};
|
websocket.logger.DEBUG("Synchronized Channel state for `{s}`", .{websocket.route.path}) catch {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user