@lampdotdev/lamp 0.0.0-stage → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/Resources/Devices/roku.json +410 -0
- package/Resources/Devices/xbox-one.json +367 -0
- package/Resources/GameKit/NOTICE.md +63 -0
- package/Resources/GameKit/apple/LampPage.swift +1646 -0
- package/Resources/GameKit/apple/LampPlayer.swift +46 -0
- package/Resources/GameKit/apple/LampWebApp.swift +63 -0
- package/Resources/GameKit/apple/LampWebGame.swift +87 -0
- package/Resources/GameKit/apple/Native.swift +12 -0
- package/Resources/GameKit/build.sh +55 -0
- package/Resources/GameKit/lamp_game.h +116 -0
- package/Resources/GameKit/lamp_host.h +19 -0
- package/Resources/GameKit/lamp_kit.cpp +61 -0
- package/Resources/GameKit/lamp_sound.cpp +455 -0
- package/Resources/GameKit/lamp_xbox.cpp +374 -0
- package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
- package/Resources/GameKit/libraries/cgltf.h +7240 -0
- package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
- package/Resources/GameKit/libraries/dr_wav.h +9209 -0
- package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
- package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
- package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
- package/Resources/GameKit/libraries/sokol_log.h +335 -0
- package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
- package/Resources/GameKit/libraries/stb_image.h +7988 -0
- package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
- package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
- package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
- package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
- package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
- package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
- package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
- package/Resources/GameKit/web/lamp-game.js +3036 -0
- package/Resources/GameKit/web/lamp-phaser.js +128 -0
- package/Resources/WebHost/App.exe +0 -0
- package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
- package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
- package/Resources/WebHost/lamp_controllers.h +1506 -0
- package/Resources/WebHost/lamp_web_host.cpp +1173 -0
- package/Resources/WebLibraries/earlier.txt +318 -0
- package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
- package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
- package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
- package/Resources/WebLibraries/qrcode/LICENSE +21 -0
- package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
- package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
- package/Resources/WebLibraries/rapier/LICENSE +201 -0
- package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
- package/Resources/WebLibraries/rapier/rapier.js +2 -0
- package/Resources/WebLibraries/three/LICENSE +21 -0
- package/Resources/WebLibraries/three/NOTICE.md +4 -0
- package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
- package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
- package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
- package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
- package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
- package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
- package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
- package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
- package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
- package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
- package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
- package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
- package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
- package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
- package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
- package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
- package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
- package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
- package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
- package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
- package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
- package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
- package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
- package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
- package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
- package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
- package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
- package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
- package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
- package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
- package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
- package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
- package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
- package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
- package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
- package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
- package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
- package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
- package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
- package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
- package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
- package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
- package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
- package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
- package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
- package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
- package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
- package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
- package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
- package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
- package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
- package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
- package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
- package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
- package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
- package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
- package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
- package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
- package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
- package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
- package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
- package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
- package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
- package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
- package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
- package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
- package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
- package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
- package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
- package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
- package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
- package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
- package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
- package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
- package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
- package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
- package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
- package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
- package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
- package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
- package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
- package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
- package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
- package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
- package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
- package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
- package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
- package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
- package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
- package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
- package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
- package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
- package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
- package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
- package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
- package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
- package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
- package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
- package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
- package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
- package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
- package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
- package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
- package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
- package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
- package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
- package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
- package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
- package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
- package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
- package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
- package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
- package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
- package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
- package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
- package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
- package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
- package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
- package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
- package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
- package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
- package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
- package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
- package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
- package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
- package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
- package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
- package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
- package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
- package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
- package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
- package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
- package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
- package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
- package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
- package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
- package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
- package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
- package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
- package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
- package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
- package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
- package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
- package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
- package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
- package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
- package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
- package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
- package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
- package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
- package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
- package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
- package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
- package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
- package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
- package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
- package/Resources/WebLibraries/three/three.core.js +61162 -0
- package/Resources/WebLibraries/three/three.module.js +19537 -0
- package/Resources/WebLibraries/three/three.tsl.js +41903 -0
- package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
- package/Resources/skills/2d-game/SKILL.md +305 -0
- package/Resources/skills/3d-game/SKILL.md +274 -0
- package/Resources/skills/accounts/SKILL.md +41 -0
- package/Resources/skills/android-design/SKILL.md +159 -0
- package/Resources/skills/app-ai/SKILL.md +154 -0
- package/Resources/skills/app-intents/SKILL.md +31 -0
- package/Resources/skills/app-kinds/SKILL.md +122 -0
- package/Resources/skills/apple-watch/SKILL.md +43 -0
- package/Resources/skills/assets/SKILL.md +183 -0
- package/Resources/skills/building-blocks/SKILL.md +97 -0
- package/Resources/skills/device-control/SKILL.md +56 -0
- package/Resources/skills/device-integrations/SKILL.md +278 -0
- package/Resources/skills/finder-files/SKILL.md +19 -0
- package/Resources/skills/game/SKILL.md +414 -0
- package/Resources/skills/game-3d/SKILL.md +12 -0
- package/Resources/skills/game-art/SKILL.md +11 -0
- package/Resources/skills/game-feel/SKILL.md +13 -0
- package/Resources/skills/game-levels/SKILL.md +12 -0
- package/Resources/skills/game-live/SKILL.md +12 -0
- package/Resources/skills/game-physics/SKILL.md +13 -0
- package/Resources/skills/game-playtest/SKILL.md +26 -0
- package/Resources/skills/game-ui/SKILL.md +278 -0
- package/Resources/skills/github-projects/SKILL.md +59 -0
- package/Resources/skills/icloud-sync/SKILL.md +30 -0
- package/Resources/skills/iphone-hardware/SKILL.md +57 -0
- package/Resources/skills/iphone-widgets/SKILL.md +53 -0
- package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
- package/Resources/skills/mac-widgets/SKILL.md +60 -0
- package/Resources/skills/maps/SKILL.md +8 -0
- package/Resources/skills/menu-bar/SKILL.md +36 -0
- package/Resources/skills/more-apps/SKILL.md +53 -0
- package/Resources/skills/phone-controllers/SKILL.md +79 -0
- package/Resources/skills/phone-pages/SKILL.md +173 -0
- package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
- package/Resources/skills/scene-3d/SKILL.md +27 -0
- package/Resources/skills/scripting/SKILL.md +17 -0
- package/Resources/skills/share-extension/SKILL.md +22 -0
- package/Resources/skills/tv-design/SKILL.md +72 -0
- package/Resources/skills/web-app/SKILL.md +136 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +103 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/android.mjs +941 -0
- package/cli/browser.mjs +268 -0
- package/cli/calls.mjs +390 -0
- package/cli/dev.mjs +773 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1016 -0
- package/cli/lampapp.mjs +332 -0
- package/cli/lamps/android-host/LampPageGateway.kt +345 -0
- package/cli/lamps/android-host/app/build.gradle.kts +40 -0
- package/cli/lamps/android-host/build.gradle.kts +4 -0
- package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
- package/cli/lamps/android-host/game/MainActivity.kt +173 -0
- package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
- package/cli/lamps/android-host/gradle.properties +8 -0
- package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
- package/cli/lamps/android-host/page/MainActivity.kt +193 -0
- package/cli/lamps/android-host/settings.gradle.kts +30 -0
- package/cli/lamps/android-host/tools.json +43 -0
- package/cli/lamps/lamp-controllers.js +2 -0
- package/cli/lamps/lamp-for-games.js +6863 -0
- package/cli/lamps/lamp.js +7023 -0
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
- package/cli/lamps/mac-starter/APP.md +12 -0
- package/cli/lamps/mac-starter/Package.swift +8 -0
- package/cli/lamps/mac-starter/Sources/App.swift +11 -0
- package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +18097 -0
- package/cli/lamps/mac-starter/build.sh +336 -0
- package/cli/lamps/mac-starter/gitignore +3 -0
- package/cli/lamps/mac-starter/lamp-starter.json +6 -0
- package/cli/lamps/web-starter/APP.md +8 -0
- package/cli/lamps/web-starter/app.js +1 -0
- package/cli/lamps/web-starter/index.html +17 -0
- package/cli/lamps/web-starter/lamp-starter.json +5 -0
- package/cli/lamps/web-starter/lamp.js +7023 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +138 -0
- package/cli/mac.mjs +621 -0
- package/cli/mdns.mjs +205 -0
- package/cli/msix.mjs +413 -0
- package/cli/network.mjs +209 -0
- package/cli/outside.mjs +140 -0
- package/cli/page.js +165 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +202 -0
- package/cli/skill/SKILL.md +255 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +173 -0
- package/cli/update.mjs +57 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +996 -0
package/cli/browser.mjs
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// A browser lamp opens and can see into: the window the person uses, or a copy with no window that's only looked at.
|
|
2
|
+
// It's a Chromium-family browser already on the computer, started with files of its own (never the person's own
|
|
3
|
+
// browser profile) and spoken to over a pipe only this process holds, so nothing else on the computer can drive it.
|
|
4
|
+
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { Problem } from "./state.mjs";
|
|
10
|
+
|
|
11
|
+
const MAC = ["Google Chrome", "Chromium", "Microsoft Edge", "Brave Browser", "Google Chrome Canary"];
|
|
12
|
+
const LINUX = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge", "brave-browser"];
|
|
13
|
+
const WINDOWS = ["Google\\Chrome\\Application\\chrome.exe", "Microsoft\\Edge\\Application\\msedge.exe", "BraveSoftware\\Brave-Browser\\Application\\brave.exe"];
|
|
14
|
+
|
|
15
|
+
/** A browser lamp can drive, or null: LAMP_BROWSER names one, else the first of the usual ones found. */
|
|
16
|
+
export function findBrowser() {
|
|
17
|
+
const named = process.env.LAMP_BROWSER;
|
|
18
|
+
if (named !== undefined) return named && existsSync(named) ? named : null;
|
|
19
|
+
if (process.platform === "darwin") {
|
|
20
|
+
for (const apps of ["/Applications", join(homedir(), "Applications")]) {
|
|
21
|
+
for (const name of MAC) {
|
|
22
|
+
const path = join(apps, `${name}.app`, "Contents", "MacOS", name);
|
|
23
|
+
if (existsSync(path)) return path;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (process.platform === "win32") {
|
|
29
|
+
for (const base of [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA]) {
|
|
30
|
+
for (const name of WINDOWS) if (base && existsSync(join(base, name))) return join(base, name);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
for (const name of LINUX) {
|
|
35
|
+
const found = spawnSync("which", [name], { encoding: "utf8" });
|
|
36
|
+
if (found.status === 0 && found.stdout.trim()) return found.stdout.trim();
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
42
|
+
|
|
43
|
+
// The keys a person names, as the browser is told them: the key, its place on the keyboard, its number, and what it
|
|
44
|
+
// types if anything.
|
|
45
|
+
const NAMED = {
|
|
46
|
+
enter: ["Enter", "Enter", 13, "\r"], return: ["Enter", "Enter", 13, "\r"], escape: ["Escape", "Escape", 27], esc: ["Escape", "Escape", 27],
|
|
47
|
+
space: [" ", "Space", 32, " "], tab: ["Tab", "Tab", 9], backspace: ["Backspace", "Backspace", 8], delete: ["Delete", "Delete", 46],
|
|
48
|
+
up: ["ArrowUp", "ArrowUp", 38], down: ["ArrowDown", "ArrowDown", 40], left: ["ArrowLeft", "ArrowLeft", 37], right: ["ArrowRight", "ArrowRight", 39],
|
|
49
|
+
shift: ["Shift", "ShiftLeft", 16], control: ["Control", "ControlLeft", 17], ctrl: ["Control", "ControlLeft", 17],
|
|
50
|
+
alt: ["Alt", "AltLeft", 18], option: ["Alt", "AltLeft", 18], command: ["Meta", "MetaLeft", 91], meta: ["Meta", "MetaLeft", 91],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// The browser's bit for each modifier key while it's held.
|
|
54
|
+
const MODIFIERS = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
|
|
55
|
+
|
|
56
|
+
export function keyNamed(name) {
|
|
57
|
+
const lower = String(name).toLowerCase();
|
|
58
|
+
const named = NAMED[lower] ?? NAMED[lower.replace(/^arrow/, "")];
|
|
59
|
+
if (named) return { key: named[0], code: named[1], number: named[2], text: named[3] };
|
|
60
|
+
if (/^[a-z]$/.test(lower)) return { key: lower, code: `Key${lower.toUpperCase()}`, number: lower.toUpperCase().charCodeAt(0), text: lower };
|
|
61
|
+
if (/^[0-9]$/.test(lower)) return { key: lower, code: `Digit${lower}`, number: 48 + Number(lower), text: lower };
|
|
62
|
+
const f = /^f([1-9]|1[0-2])$/.exec(lower);
|
|
63
|
+
if (f) return { key: `F${f[1]}`, code: `F${f[1]}`, number: 111 + Number(f[1]) };
|
|
64
|
+
throw new Problem(`lamp doesn't know the key "${name}": a letter, a digit, F1 to F12, or one of ${Object.keys(NAMED).join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One page of the browser, as lamp looks at it and uses it. */
|
|
68
|
+
class Page {
|
|
69
|
+
constructor(browser, target, session) { this.browser = browser; this.target = target; this.session = session; }
|
|
70
|
+
|
|
71
|
+
send(method, params, timeout) { return this.browser.send(method, params, this.session, timeout); }
|
|
72
|
+
|
|
73
|
+
/** What an expression gives in the page, as JSON; null when the page can't answer in time. */
|
|
74
|
+
async evaluate(expression, { timeout = 5000 } = {}) {
|
|
75
|
+
const answer = await this.send("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true }, timeout).catch(() => null);
|
|
76
|
+
return answer?.result?.value ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A picture of the page as it is, one pixel of it for each point of the page, so a point of the picture is a point to click. */
|
|
80
|
+
async picture({ width, height, dpr = 1, jpeg = false }) {
|
|
81
|
+
const clip = width && height ? { x: 0, y: 0, width, height, scale: 1 / dpr } : undefined;
|
|
82
|
+
const shot = await this.send("Page.captureScreenshot", { ...(jpeg ? { format: "jpeg", quality: 85 } : { format: "png" }), ...(clip ? { clip } : {}) }, 8000);
|
|
83
|
+
return Buffer.from(shot.data, "base64");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The page answers as the page in front, with the keyboard its own, however it's shown. */
|
|
87
|
+
async focus() { await this.send("Emulation.setFocusEmulationEnabled", { enabled: true }).catch(() => {}); }
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Holds a key, or several at once (`w+a`, `control+a`), for some seconds. Each key goes down with the modifiers held
|
|
91
|
+
* before it, as a keyboard sends them: with Control, Alt or Command held it's a shortcut and types nothing, and with
|
|
92
|
+
* Shift a letter is a capital.
|
|
93
|
+
*/
|
|
94
|
+
async key(names, seconds = 0) {
|
|
95
|
+
const keys = String(names).split("+").filter(Boolean).map(keyNamed);
|
|
96
|
+
let held = 0;
|
|
97
|
+
const sent = [];
|
|
98
|
+
for (const { key, code, number, text } of keys) {
|
|
99
|
+
const shifted = held & 8 && /^[a-z]$/.test(key) ? key.toUpperCase() : key;
|
|
100
|
+
const typed = held & 7 ? null : text && held & 8 && /^[a-z]$/.test(text) ? text.toUpperCase() : text;
|
|
101
|
+
const common = { key: shifted, code, windowsVirtualKeyCode: number, nativeVirtualKeyCode: number };
|
|
102
|
+
await this.send("Input.dispatchKeyEvent", { type: typed ? "keyDown" : "rawKeyDown", modifiers: held, ...common, ...(typed ? { text: typed, unmodifiedText: text } : {}) });
|
|
103
|
+
held |= MODIFIERS[key] ?? 0;
|
|
104
|
+
sent.push(common);
|
|
105
|
+
}
|
|
106
|
+
await sleep(Math.max(40, seconds * 1000));
|
|
107
|
+
for (const common of sent.reverse()) {
|
|
108
|
+
held &= ~(MODIFIERS[common.key] ?? 0);
|
|
109
|
+
await this.send("Input.dispatchKeyEvent", { type: "keyUp", modifiers: held, ...common });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async click(x, y) {
|
|
114
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x, y });
|
|
115
|
+
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", buttons: 1, clickCount: 1 });
|
|
116
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", buttons: 0, clickCount: 1 });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async type(text) { await this.send("Input.insertText", { text }); }
|
|
120
|
+
|
|
121
|
+
/** Presses at one point, moves to another and lets go, as a hand would: in steps. */
|
|
122
|
+
async drag(x, y, toX, toY) {
|
|
123
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x, y });
|
|
124
|
+
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", buttons: 1, clickCount: 1 });
|
|
125
|
+
for (let step = 1; step <= 12; step++) {
|
|
126
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: x + ((toX - x) * step) / 12, y: y + ((toY - y) * step) / 12, button: "left", buttons: 1 });
|
|
127
|
+
await sleep(16);
|
|
128
|
+
}
|
|
129
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: toX, y: toY, button: "left", buttons: 0, clickCount: 1 });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async scroll(x, y, points) { await this.send("Input.dispatchMouseEvent", { type: "mouseWheel", x, y, deltaX: 0, deltaY: points }); }
|
|
133
|
+
|
|
134
|
+
async front() { await this.send("Page.bringToFront").catch(() => {}); }
|
|
135
|
+
|
|
136
|
+
/** Shows the page at another size, as a window of that size would, or at its own again with none given. */
|
|
137
|
+
async size(width, height) {
|
|
138
|
+
if (width && height) await this.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 0, mobile: false });
|
|
139
|
+
else await this.send("Emulation.clearDeviceMetricsOverride").catch(() => {});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class Browser {
|
|
144
|
+
constructor(child) {
|
|
145
|
+
this.child = child;
|
|
146
|
+
this.alive = true;
|
|
147
|
+
this.count = 0;
|
|
148
|
+
this.waiting = new Map();
|
|
149
|
+
this.pages = new Map(); // target id → Page
|
|
150
|
+
this.gone = [];
|
|
151
|
+
let held = "";
|
|
152
|
+
// The pipe's messages are JSON, each ended by a zero byte.
|
|
153
|
+
child.stdio[4].setEncoding("utf8");
|
|
154
|
+
child.stdio[4].on("data", (chunk) => {
|
|
155
|
+
held += chunk;
|
|
156
|
+
let end;
|
|
157
|
+
while ((end = held.indexOf("\0")) >= 0) {
|
|
158
|
+
const text = held.slice(0, end);
|
|
159
|
+
held = held.slice(end + 1);
|
|
160
|
+
let message;
|
|
161
|
+
try { message = JSON.parse(text); } catch { continue; }
|
|
162
|
+
this.heard(message);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
child.stdio[3].on("error", () => {});
|
|
166
|
+
child.stdio[4].on("error", () => {});
|
|
167
|
+
child.on("exit", () => this.ended());
|
|
168
|
+
child.on("error", () => this.ended());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
heard(message) {
|
|
172
|
+
if (message.id !== undefined) {
|
|
173
|
+
const waiter = this.waiting.get(message.id);
|
|
174
|
+
if (!waiter) return;
|
|
175
|
+
this.waiting.delete(message.id);
|
|
176
|
+
clearTimeout(waiter.timer);
|
|
177
|
+
if (message.error) waiter.reject(new Error(message.error.message)); else waiter.resolve(message.result);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (message.method === "Target.targetDestroyed" || message.method === "Target.detachedFromTarget") {
|
|
181
|
+
const id = message.params.targetId;
|
|
182
|
+
if (id) this.pages.delete(id);
|
|
183
|
+
// The person closed the window: on a Mac the browser would stay running with none, so it's closed with it.
|
|
184
|
+
if (message.method === "Target.targetDestroyed") this.closeIfEmpty();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async closeIfEmpty() {
|
|
189
|
+
const targets = await this.send("Target.getTargets").catch(() => null);
|
|
190
|
+
if (targets && !targets.targetInfos.some((target) => target.type === "page")) this.close();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
ended() {
|
|
194
|
+
if (!this.alive) return;
|
|
195
|
+
this.alive = false;
|
|
196
|
+
for (const waiter of this.waiting.values()) { clearTimeout(waiter.timer); waiter.reject(new Error("the browser closed")); }
|
|
197
|
+
this.waiting.clear();
|
|
198
|
+
this.pages.clear();
|
|
199
|
+
for (const told of this.gone) told();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Called once the browser has gone, however it went. */
|
|
203
|
+
onGone(told) { this.gone.push(told); }
|
|
204
|
+
|
|
205
|
+
send(method, params = {}, session, timeout = 10_000) {
|
|
206
|
+
if (!this.alive) return Promise.reject(new Error("the browser closed"));
|
|
207
|
+
const id = ++this.count;
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const timer = setTimeout(() => { this.waiting.delete(id); reject(new Error(`the browser didn't answer ${method}`)); }, timeout);
|
|
210
|
+
this.waiting.set(id, { resolve, reject, timer });
|
|
211
|
+
this.child.stdio[3].write(JSON.stringify({ id, method, params, ...(session ? { sessionId: session } : {}) }) + "\0");
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The page showing what's at this address, or null when the browser has none (the person went elsewhere in it). */
|
|
216
|
+
async page(origin, timeout) {
|
|
217
|
+
if (!this.alive) return null;
|
|
218
|
+
const targets = await this.send("Target.getTargets", {}, undefined, timeout).catch(() => null);
|
|
219
|
+
const target = targets?.targetInfos.find((info) => info.type === "page" && info.url.startsWith(origin));
|
|
220
|
+
if (!target) return null;
|
|
221
|
+
let page = this.pages.get(target.targetId);
|
|
222
|
+
if (!page) {
|
|
223
|
+
const attached = await this.send("Target.attachToTarget", { targetId: target.targetId, flatten: true });
|
|
224
|
+
page = new Page(this, target.targetId, attached.sessionId);
|
|
225
|
+
this.pages.set(target.targetId, page);
|
|
226
|
+
}
|
|
227
|
+
return page;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
close() {
|
|
231
|
+
if (!this.alive) return;
|
|
232
|
+
this.send("Browser.close").catch(() => {});
|
|
233
|
+
setTimeout(() => { try { this.child.kill(); } catch {} }, 1500).unref();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** At once, as lamp itself goes. */
|
|
237
|
+
kill() { try { this.child.kill(); } catch {} }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Starts the browser at an address: `window` a window for the person, else a copy that's never shown. Its files are
|
|
242
|
+
* kept in `profile`, apart from the person's own browser.
|
|
243
|
+
*/
|
|
244
|
+
export async function launch({ path, url, profile, window, size = [1280, 800] }) {
|
|
245
|
+
mkdirSync(profile, { recursive: true });
|
|
246
|
+
const args = [
|
|
247
|
+
"--remote-debugging-pipe", `--user-data-dir=${profile}`, "--no-first-run", "--no-default-browser-check",
|
|
248
|
+
"--disable-features=Translate,MediaRouter,OptimizationHints", "--disable-sync", "--disable-component-update", "--hide-crash-restore-bubble",
|
|
249
|
+
`--window-size=${size[0]},${size[1]}`,
|
|
250
|
+
// The page goes on running, and takes changes, while it's behind other windows.
|
|
251
|
+
"--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding", "--disable-background-timer-throttling",
|
|
252
|
+
...(window ? [`--app=${url}`] : ["--headless=new", "--hide-scrollbars", "--mute-audio", url]),
|
|
253
|
+
];
|
|
254
|
+
let child;
|
|
255
|
+
try { child = spawn(path, args, { stdio: ["ignore", "ignore", "ignore", "pipe", "pipe"] }); }
|
|
256
|
+
catch (error) { throw new Problem(`${path} didn't start: ${error.message}`); }
|
|
257
|
+
const browser = new Browser(child);
|
|
258
|
+
// Target ids come with what happens to them, so a closed window is noticed.
|
|
259
|
+
await browser.send("Target.setDiscoverTargets", { discover: true }).catch(() => {});
|
|
260
|
+
const origin = new URL(url).origin;
|
|
261
|
+
const until = Date.now() + 15_000;
|
|
262
|
+
while (browser.alive && Date.now() < until) {
|
|
263
|
+
if (await browser.page(origin, 2000)) return browser;
|
|
264
|
+
await sleep(100);
|
|
265
|
+
}
|
|
266
|
+
browser.kill();
|
|
267
|
+
throw new Problem("the browser started but didn't open the page");
|
|
268
|
+
}
|
package/cli/calls.mjs
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// What Lamp's app asks of lamp. The work that's lamp's own (this folder) is done here once, and Lamp's app has it done
|
|
3
|
+
// (`LampCLI`, in Swift) rather than doing it a second time itself. Each call is one run of this file: the call's name
|
|
4
|
+
// is its one argument, what it's given is one JSON object on standard input (never in the arguments, which any
|
|
5
|
+
// process on the computer can list), and its answer is the last line it prints, one JSON object: {"done": …}, or
|
|
6
|
+
// {"problem": "…"} for something the person can put right, said in a sentence. Anything else that goes wrong ends
|
|
7
|
+
// the run with its own words on standard error.
|
|
8
|
+
//
|
|
9
|
+
// A call works on what Lamp's app hands it from Lamp's own folders, by path. It's never pointed at an app's folder:
|
|
10
|
+
// what's there is the agent's to write, and Lamp's app reads it its own way before anything comes here.
|
|
11
|
+
//
|
|
12
|
+
// Run with --serve in the place of a call's name, it stays: each line on standard input is {"id", "call", "given"},
|
|
13
|
+
// and each is answered, in whatever order they finish, by a line {"id", "done"} or {"id", "problem"}, or {"id",
|
|
14
|
+
// "failed"} with lamp's own words for anything else. A line {"stop": id} lets go of a call that hasn't answered, when
|
|
15
|
+
// whoever asked no longer wants it. It ends when its input does, which is when Lamp's app does. The many small things
|
|
16
|
+
// go this way (a request to a device), so each doesn't wait for a program to start.
|
|
17
|
+
//
|
|
18
|
+
// Lamp's app runs this on the JavaScript runtime it carries (the Bun its bundled agent is built on), and a test run on
|
|
19
|
+
// Node: what's imported here, and by what it imports, has to run on both.
|
|
20
|
+
|
|
21
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { Away, OffNetwork, Refused, Rejected, Runner, StillWorking, answersOn, bonjour, pinsAt, searchNetwork, send, ssdp } from "./devices.mjs";
|
|
25
|
+
import { adbCommand, connectTo, deviceListing, deviceProperties, displaySizeOf, dumpIn, installApk, installTools, launchApp, makeEmulator, pairWith, processIn, processOf,
|
|
26
|
+
rowsOf, saveState, screenItems, screenshotOf, tellApp, toolsThere, useLamps } from "./android.mjs";
|
|
27
|
+
import { Unreachable, ideasOf, latestOf, publishLink, removeFile, removeIdea, removeLink, sendFile } from "./link.mjs";
|
|
28
|
+
import { defaultRoute, homeNetwork, neighbours, samsungAt, subnetOn, tvsIn } from "./network.mjs";
|
|
29
|
+
import { PUBLISHER, identityName, keptSigner, namesOf, packageOf, versionAfter } from "./msix.mjs";
|
|
30
|
+
import { tile } from "./png.mjs";
|
|
31
|
+
import { Problem } from "./state.mjs";
|
|
32
|
+
|
|
33
|
+
/** A number of seconds a call was given, within what it may take. */
|
|
34
|
+
const seconds = (given, standard, most = 10) => Math.min(Math.max(Number(given) > 0 ? Number(given) : standard, 0.5), most);
|
|
35
|
+
|
|
36
|
+
const text = (given, name) => {
|
|
37
|
+
if (typeof given[name] !== "string" || !given[name]) throw new Error(`the call needs "${name}"`);
|
|
38
|
+
return given[name];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Every file in a folder, by its path from the folder's top, in a settled order. A link is never followed: it's refused. */
|
|
42
|
+
function filesIn(folder) {
|
|
43
|
+
const files = [];
|
|
44
|
+
const walk = (at, prefix) => {
|
|
45
|
+
for (const name of readdirSync(at).sort()) {
|
|
46
|
+
const file = join(at, name), kind = lstatSync(file);
|
|
47
|
+
if (kind.isSymbolicLink()) throw new Problem(`${prefix}${name} is a shortcut to another file. Put the file itself in its place`);
|
|
48
|
+
if (kind.isDirectory()) walk(file, `${prefix}${name}/`);
|
|
49
|
+
else if (kind.isFile()) files.push([prefix + name, readFileSync(file)]);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(folder, "");
|
|
53
|
+
return files;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Written whole or not at all: beside its place, then moved in. */
|
|
57
|
+
function write(file, bytes, mode) {
|
|
58
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
59
|
+
const beside = `${file}.writing`;
|
|
60
|
+
writeFileSync(beside, bytes, mode ? { mode } : undefined);
|
|
61
|
+
renameSync(beside, file);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The runner for an integration's device, as Lamp's app hands it over: the integration (its JSON), the device (its
|
|
66
|
+
* id, name and address), the values to fill in (what the person typed among them, kept by Lamp's app), the file its
|
|
67
|
+
* certificates are kept in, and where a key it gives is kept. `event` tells whoever asked each time the device is
|
|
68
|
+
* waited on; `stopped` ends the run.
|
|
69
|
+
*/
|
|
70
|
+
function runnerOf(given, { stopped, event = () => {} } = {}) {
|
|
71
|
+
if (typeof given.integration?.id !== "string" || typeof given.device?.id !== "string") throw new Error('the call needs "integration" and "device"');
|
|
72
|
+
return new Runner({ integration: given.integration, device: { id: given.device.id, name: String(given.device.name ?? "The device"), address: given.device.address },
|
|
73
|
+
values: given.values ?? {}, pins: pinsAt(text(given, "pins")), keyFile: typeof given.keyFile === "string" ? given.keyFile : undefined,
|
|
74
|
+
stopped, onWaiting: () => event({ waiting: true }) });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** What a sequence gave back, as Lamp's app reads it: a picture in base64, and the cookies its next part sends back. */
|
|
78
|
+
const outcomeOf = (outcome) => ({ ...outcome, picture: outcome.picture ? Buffer.from(outcome.picture).toString("base64") : null });
|
|
79
|
+
|
|
80
|
+
/** A shared link as Lamp's app keeps it: its id and secret, and the site it's on. */
|
|
81
|
+
const linkOf = (given) => ({ id: text(given, "id"), secret: text(given, "secret") });
|
|
82
|
+
const siteOf = (given) => text(given, "site").replace(/\/+$/, "");
|
|
83
|
+
|
|
84
|
+
/** Lamp's app's Android, handed over with every adb call: its tools list, folders and environment (its own adb server and key). */
|
|
85
|
+
const androidFor = (given) => useLamps(given.lamp ?? {});
|
|
86
|
+
const serialOf = (given) => text(given, "serial");
|
|
87
|
+
|
|
88
|
+
export const CALLS = {
|
|
89
|
+
/** One adb command for Lamp's app (`args` after the serial), with `input` on adb's input when given: what it said. */
|
|
90
|
+
async adb(given) {
|
|
91
|
+
androidFor(given);
|
|
92
|
+
return adbCommand(Array.isArray(given.args) ? given.args.map(String) : [], { serial: typeof given.serial === "string" ? given.serial : undefined,
|
|
93
|
+
timeout: Math.round((Number(given.timeout) > 0 ? Number(given.timeout) : 60) * 1000), input: typeof given.input === "string" ? given.input : undefined });
|
|
94
|
+
},
|
|
95
|
+
/** The devices adb can see, each with what it says of itself when it's ready, and what adb's search sees on the network. */
|
|
96
|
+
async androidDevices(given) { androidFor(given); return deviceListing(); },
|
|
97
|
+
/** adb's listing read into rows, for what Lamp's app was handed some other way. */
|
|
98
|
+
async androidRows(given) { return { rows: rowsOf(text(given, "listing")) }; },
|
|
99
|
+
/** What one device says of itself now. */
|
|
100
|
+
async androidProperties(given) { androidFor(given); return { properties: await deviceProperties(serialOf(given)) }; },
|
|
101
|
+
async androidConnect(given) { androidFor(given); return { connected: await connectTo(text(given, "target")) }; },
|
|
102
|
+
/** Pairs with a device by its code, which comes on the call's input and goes on adb's. */
|
|
103
|
+
async androidPair(given) { androidFor(given); return { pairing: await pairWith(text(given, "host"), Number(given.port), text(given, "code")) }; },
|
|
104
|
+
async androidInstallApp(given) { androidFor(given); await installApk(serialOf(given), text(given, "apk"), text(given, "package")); return {}; },
|
|
105
|
+
async androidTell(given) { androidFor(given); return { problem: await tellApp(serialOf(given), text(given, "package"), given.values ?? {}) }; },
|
|
106
|
+
async androidSaveState(given) { androidFor(given); return { saved: await saveState(serialOf(given), text(given, "package")) }; },
|
|
107
|
+
async androidLaunch(given) { androidFor(given); return { pid: await launchApp(serialOf(given), text(given, "package")) }; },
|
|
108
|
+
/** Whether the app runs on the device: running (with its process), gone, or unreachable. With `said`, what pidof said, read without asking. */
|
|
109
|
+
async androidProcess(given) {
|
|
110
|
+
if (given.said) return processIn(given.said);
|
|
111
|
+
androidFor(given);
|
|
112
|
+
return processOf(serialOf(given), text(given, "package"));
|
|
113
|
+
},
|
|
114
|
+
/** The screen as a PNG in base64, a pixel to a point by the device's density. */
|
|
115
|
+
async androidScreenshot(given) {
|
|
116
|
+
androidFor(given);
|
|
117
|
+
const png = await screenshotOf(serialOf(given), given.density);
|
|
118
|
+
return { png: png ? png.toString("base64") : null };
|
|
119
|
+
},
|
|
120
|
+
async androidDisplaySize(given) { androidFor(given); return { size: await displaySizeOf(serialOf(given)) }; },
|
|
121
|
+
/** The screen's dump, as `uiautomator dump` gave it (`said`), read into what's on it, in points at `scale`; null items when it held no dump. */
|
|
122
|
+
async androidScreenItems(given) {
|
|
123
|
+
const xml = dumpIn(text(given, "said"));
|
|
124
|
+
return { items: xml ? screenItems(xml, Number(given.scale) > 0 ? Number(given.scale) : 1) : null };
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Downloads whichever of Android's tools Lamp's app is missing, into its tools folder, as its tools list says (each
|
|
130
|
+
* archive checked against what its maker publishes, then Google's packages through its own package manager, which
|
|
131
|
+
* accepts Google's terms for them: only when the person asked). With `emulator`, the emulator and its phone's image
|
|
132
|
+
* too; with `tv`, the TV's image. `lamp` is Lamp's app's tools list, folders and environment. Tells how far along it
|
|
133
|
+
* is (`part`, 0 to 1) and what it's doing (`said`) as it goes; answers which tools are there.
|
|
134
|
+
*/
|
|
135
|
+
async androidInstall(given, how = {}) {
|
|
136
|
+
useLamps(given.lamp ?? {});
|
|
137
|
+
return { there: await installTools({ emulator: given.emulator === true, tv: given.tv === true,
|
|
138
|
+
said: (line) => how.event?.({ said: line }), progress: (part) => how.event?.({ part }) }) };
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/** Makes Lamp's app's emulator of a kind ("phone" or "tv") from its downloaded system image. */
|
|
142
|
+
async androidMakeEmulator(given) {
|
|
143
|
+
useLamps(given.lamp ?? {});
|
|
144
|
+
if (given.kind !== "phone" && given.kind !== "tv") throw new Error('the call needs "kind": "phone" or "tv"');
|
|
145
|
+
await makeEmulator(given.kind);
|
|
146
|
+
return { there: toolsThere() };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Shares a wish's link, or changes what it shows (`earlier`: its id and secret), on `site`, with `body` as the site
|
|
152
|
+
* takes it. Its id, address and secret.
|
|
153
|
+
*/
|
|
154
|
+
async linkPublish(given) {
|
|
155
|
+
const earlier = given.earlier && typeof given.earlier.id === "string" ? given.earlier : null;
|
|
156
|
+
return publishLink({ site: siteOf(given), body: given.body ?? {}, earlier });
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/** What a followed link has now, asked with nothing of the person's; `latest` null when it can't be asked or is gone. */
|
|
160
|
+
async linkLatest(given) {
|
|
161
|
+
return { latest: await latestOf({ site: siteOf(given), id: text(given, "id") }) };
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/** The link's file (or with `page`, its playable page), from a file of Lamp's at `file`, sent in parts. */
|
|
165
|
+
async linkUpload(given, how = {}) {
|
|
166
|
+
return { version: await sendFile({ site: siteOf(given), link: linkOf(given), bytes: readFileSync(text(given, "file")), page: given.page === true,
|
|
167
|
+
said: (line) => how.event?.({ said: line }) }) };
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/** Takes the link's file away (with `page`, only its page). */
|
|
171
|
+
async linkRemoveFile(given) {
|
|
172
|
+
await removeFile({ site: siteOf(given), link: linkOf(given), page: given.page === true });
|
|
173
|
+
return {};
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/** The ideas sent to the link that are still open. */
|
|
177
|
+
async linkIdeas(given) {
|
|
178
|
+
return { ideas: await ideasOf({ site: siteOf(given), link: linkOf(given) }) };
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/** Lets an idea go; with `built` it's in, and the site writes to whoever asked to hear. */
|
|
182
|
+
async linkRemoveIdea(given) {
|
|
183
|
+
return removeIdea({ site: siteOf(given), link: linkOf(given), n: Number(given.n), built: given.built === true });
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/** Takes the link down. */
|
|
187
|
+
async linkRemove(given) {
|
|
188
|
+
await removeLink({ site: siteOf(given), link: linkOf(given) });
|
|
189
|
+
return {};
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* One part of an integration's sequence on a device (`sequence`: its requests, waits and probes; what only Lamp's
|
|
195
|
+
* app does, a command on the device or a sign-in page, is between parts), with `cookies` from the part before. The
|
|
196
|
+
* settings it changes for itself (`restore`) come back in `changed`, which Lamp's app hands to `putBack` once it's
|
|
197
|
+
* done with them, or they're put back as the part ends when it asks for that (`putBack: true`).
|
|
198
|
+
*/
|
|
199
|
+
async run(given, how = {}) {
|
|
200
|
+
const runner = runnerOf(given, how), changed = Array.isArray(given.changed) ? given.changed : [], before = changed.length;
|
|
201
|
+
try {
|
|
202
|
+
const outcome = await runner.run(Array.isArray(given.sequence) ? given.sequence : [], { extra: given.extra ?? {}, files: given.files ?? {}, changed, cookies: given.cookies ?? {} });
|
|
203
|
+
if (given.putBack === true) await runner.putBack(changed);
|
|
204
|
+
return { outcome: outcomeOf(outcome), changed };
|
|
205
|
+
} catch (error) {
|
|
206
|
+
// A part that fails puts back what it changed itself: the answer that says so carries no settings to hand back.
|
|
207
|
+
await runner.putBack(changed.splice(before));
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/** Puts back what parts of a sequence changed for themselves (`changed`, as `run` gave it), last first: the settings it couldn't. */
|
|
213
|
+
async putBack(given, how = {}) {
|
|
214
|
+
return { missed: await runnerOf(given, how).putBack(Array.isArray(given.changed) ? given.changed : []) };
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Opens an app just put on the device: `launch`, then `running` until it's seen running, launched again while the
|
|
219
|
+
* device refuses or doesn't open it, up to `tries` launches. Whether it opened, whether it was ever launched, and
|
|
220
|
+
* what the device last refused with.
|
|
221
|
+
*/
|
|
222
|
+
async open(given, how = {}) {
|
|
223
|
+
const changed = Array.isArray(given.changed) ? given.changed : [];
|
|
224
|
+
const opened = await runnerOf(given, how).open(given.launch ?? [], given.running ?? null, { extra: given.extra ?? {}, changed, tries: Number.isInteger(given.tries) ? given.tries : 8 });
|
|
225
|
+
return { ...opened, refused: opened.refused ?? null, changed };
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/** What a check of a setup's `conditions` finds on the device now: holds, waiting, away or rejected, with why and what it read. */
|
|
229
|
+
async check(given, how = {}) {
|
|
230
|
+
return runnerOf(given, how).evaluate(Array.isArray(given.conditions) ? given.conditions : []);
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A signed Windows package (msix.mjs) of the files in `payload`, a folder Lamp's app laid out, written to `out`, with
|
|
235
|
+
* the certificate a device installs beside it at `certificate`. The package is named for the app (`slug`) and is a
|
|
236
|
+
* newer version than `after`, the last one made; `entry` is what it starts ("webHost", {page} or {program}), `icon`
|
|
237
|
+
* a picture of the app for its tiles, and `signer` the folder lamp's key for packages is kept in, made the first time.
|
|
238
|
+
*/
|
|
239
|
+
msix(given) {
|
|
240
|
+
const entry = given.entry === "webHost" || typeof given.entry?.page === "string" || typeof given.entry?.program === "string" ? given.entry : null;
|
|
241
|
+
if (!entry) throw new Error('the call needs "entry": "webHost", {page} or {program}');
|
|
242
|
+
const identity = { name: identityName(text(given, "slug")), publisher: PUBLISHER, version: versionAfter(typeof given.after === "string" ? given.after : undefined) };
|
|
243
|
+
if (typeof given.architecture === "string") identity.architecture = given.architecture;
|
|
244
|
+
let icon = null;
|
|
245
|
+
if (typeof given.icon === "string") { try { icon = readFileSync(given.icon); } catch { /* the tile's color alone */ } }
|
|
246
|
+
const payload = filesIn(text(given, "payload")), signer = keptSigner(text(given, "signer"));
|
|
247
|
+
const made = packageOf({ payload, displayName: text(given, "displayName"), identity, entry, logo: (width, height) => tile(icon, width, height), signer });
|
|
248
|
+
write(text(given, "out"), made);
|
|
249
|
+
write(text(given, "certificate"), signer.certificate);
|
|
250
|
+
return { identity, ...namesOf(identity), bytes: made.length, files: payload.length };
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* One request of an integration's to a device (devices.mjs's `send`, with every guard it has): `request` as the
|
|
255
|
+
* integration writes one, to the device at `address`, with `values` filled in (what the person typed for the device
|
|
256
|
+
* among them, which is why this comes on standard input), `files` it may send by path, and `pins`, the file the
|
|
257
|
+
* device's certificates are kept in, without which nothing secret goes over HTTPS. The answer's body is in base64.
|
|
258
|
+
*/
|
|
259
|
+
async send(given, { stopped } = {}) {
|
|
260
|
+
const answer = await send(given.request ?? {}, text(given, "address"), given.values ?? {}, {
|
|
261
|
+
files: given.files ?? {}, pins: typeof given.pins === "string" ? pinsAt(given.pins) : undefined,
|
|
262
|
+
timeout: Math.round((Number(given.timeout) > 0 ? Number(given.timeout) : 30) * 1000), stopped,
|
|
263
|
+
});
|
|
264
|
+
return { status: answer.status, body: answer.body.toString("base64"), cookies: answer.cookies, certificate: answer.certificate?.toString("base64") ?? null };
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* One look at the home network for the `integrations` Lamp's app has (each as its JSON): every answer to a search
|
|
269
|
+
* for everything, what each integration found and read of its devices, with `unknown` the devices none of them
|
|
270
|
+
* knows, read from their own descriptions, and with `tvs` what each Samsung or LG TV that answered says of itself.
|
|
271
|
+
* Which of those Lamp keeps, and what it called them, is Lamp's app's.
|
|
272
|
+
*/
|
|
273
|
+
async look(given) {
|
|
274
|
+
const list = (Array.isArray(given.integrations) ? given.integrations : []).filter((integration) => typeof integration?.id === "string");
|
|
275
|
+
const answers = await ssdp("ssdp:all", seconds(given.seconds, 2.5));
|
|
276
|
+
const [seen, tvs] = await Promise.all([searchNetwork({ integrations: list, answers, unknown: given.unknown === true }), given.tvs === true ? tvsIn(answers) : []]);
|
|
277
|
+
return { ...seen, tvs };
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/** What a Samsung TV at `address` says of itself now, as the look reads it; null when it doesn't answer as one. */
|
|
281
|
+
async tv(given) {
|
|
282
|
+
return { tv: await samsungAt(text(given, "address")) };
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Everything on this computer's subnet besides what the look finds: each host with its hardware address and its
|
|
287
|
+
* Bonjour services, and which subnet was swept.
|
|
288
|
+
*/
|
|
289
|
+
async network(given) {
|
|
290
|
+
return homeNetwork({ seconds: seconds(given.seconds, 3) });
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The network as it is now, read with nothing sent: this computer's neighbor table (each neighbor's IPv4 address
|
|
295
|
+
* and hardware address), its gateway, and the subnet a sweep would take.
|
|
296
|
+
*/
|
|
297
|
+
async neighbours() {
|
|
298
|
+
const route = await defaultRoute();
|
|
299
|
+
return { table: await neighbours(), gateway: route?.gateway ?? null, subnet: route ? subnetOn(route.interface) : null };
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
/** A search of the home network for one search target, for a few seconds: every answer. */
|
|
303
|
+
async search(given) {
|
|
304
|
+
return { answers: await ssdp(typeof given.target === "string" ? given.target : "ssdp:all", seconds(given.seconds, 2)) };
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Whether the device at `address` is there now, as a step's check asks: it takes a connection on a port (`tcp`), it
|
|
309
|
+
* answers a search for a target (`ssdp`), or it offers a Bonjour service (`bonjour`).
|
|
310
|
+
*/
|
|
311
|
+
async probe(given) {
|
|
312
|
+
const address = text(given, "address");
|
|
313
|
+
if (Number.isInteger(given.tcp)) return { present: await answersOn(address, given.tcp, { timeout: seconds(given.seconds, 3) * 1000 }) };
|
|
314
|
+
if (typeof given.ssdp === "string") return { present: (await ssdp(given.ssdp, seconds(given.seconds, 2))).some((answer) => answer.address === address) };
|
|
315
|
+
if (typeof given.bonjour === "string") return { present: (await bonjour(given.bonjour, seconds(given.seconds, 3))).some((found) => found.address === address) };
|
|
316
|
+
throw new Error('the call needs "tcp", "ssdp" or "bonjour"');
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/** A problem as Lamp's app shows one: a sentence, in which lamp is Lamp. */
|
|
321
|
+
const sentence = (words) => `${words[0].toUpperCase()}${words.slice(1)}${/[.!?]$/.test(words) ? "" : "."}`.replace(/\blamp\b/g, "Lamp");
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* What went wrong, as the answer says it: a problem the person can put right, with its kind where Lamp's app does
|
|
325
|
+
* something about that kind (a certificate that changed, a device that's away); null for anything else, lamp's own fault.
|
|
326
|
+
*/
|
|
327
|
+
function problemOf(error) {
|
|
328
|
+
if (!(error instanceof Problem)) return null;
|
|
329
|
+
const said = { problem: sentence(error.message) };
|
|
330
|
+
if (error instanceof Refused) said.kind = error.kind;
|
|
331
|
+
else if (error instanceof Rejected) said.kind = "rejected";
|
|
332
|
+
else if (error instanceof StillWorking) said.kind = "still-working";
|
|
333
|
+
else if (error instanceof OffNetwork) said.kind = "off-network";
|
|
334
|
+
else if (error instanceof Unreachable) said.kind = "unreachable";
|
|
335
|
+
else if (error instanceof Away) Object.assign(said, { kind: "away", code: error.code ?? null, timedOut: error.timedOut });
|
|
336
|
+
if (Number.isInteger(error.status)) said.status = error.status;
|
|
337
|
+
return said;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** One call, run to its end, as a single run of this file. */
|
|
341
|
+
async function once(name) {
|
|
342
|
+
const call = CALLS[name];
|
|
343
|
+
if (!call) throw new Error(`lamp has no call named "${name ?? ""}"`);
|
|
344
|
+
const chunks = [];
|
|
345
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
346
|
+
const given = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
347
|
+
try {
|
|
348
|
+
console.log(JSON.stringify({ done: await call(given) }));
|
|
349
|
+
} catch (error) {
|
|
350
|
+
const said = problemOf(error);
|
|
351
|
+
if (!said) throw error;
|
|
352
|
+
console.log(JSON.stringify(said));
|
|
353
|
+
process.exitCode = 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Calls answered as they come, for as long as there's someone to ask. */
|
|
358
|
+
async function serve() {
|
|
359
|
+
const say = (line) => process.stdout.write(JSON.stringify(line) + "\n");
|
|
360
|
+
const under = new Map();
|
|
361
|
+
const answer = async (line) => {
|
|
362
|
+
let asked;
|
|
363
|
+
try { asked = JSON.parse(line); } catch { return; }
|
|
364
|
+
if (asked?.stop !== undefined) return void under.get(asked.stop)?.abort();
|
|
365
|
+
const id = asked?.id, call = CALLS[asked?.call], stopping = new AbortController();
|
|
366
|
+
under.set(id, stopping);
|
|
367
|
+
try {
|
|
368
|
+
if (!call) throw new Error(`lamp has no call named "${asked?.call ?? ""}"`);
|
|
369
|
+
say({ id, done: await call(asked.given ?? {}, { stopped: stopping.signal, event: (said) => say({ id, event: said }) }) });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
say({ id, ...(problemOf(error) ?? { failed: String(error?.stack ?? error) }) });
|
|
372
|
+
} finally { under.delete(id); }
|
|
373
|
+
};
|
|
374
|
+
let waiting = [];
|
|
375
|
+
for await (const chunk of process.stdin) {
|
|
376
|
+
let from = 0;
|
|
377
|
+
for (let end = chunk.indexOf(10, from); end >= 0; end = chunk.indexOf(10, from)) {
|
|
378
|
+
const line = Buffer.concat([...waiting, chunk.subarray(from, end)]).toString("utf8");
|
|
379
|
+
waiting = [];
|
|
380
|
+
from = end + 1;
|
|
381
|
+
if (line.trim()) answer(line);
|
|
382
|
+
}
|
|
383
|
+
if (from < chunk.length) waiting.push(chunk.subarray(from));
|
|
384
|
+
}
|
|
385
|
+
// Nobody is left to hear an answer.
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const here = () => { try { return realpathSync(process.argv[1] ?? "") === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } };
|
|
390
|
+
if (here()) await (process.argv[2] === "--serve" ? serve() : once(process.argv[2]));
|