@php-wasm/node 0.6.16 → 0.7.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.
Files changed (3) hide show
  1. package/index.cjs +249 -165
  2. package/index.d.ts +93 -185
  3. package/package.json +5 -5
package/index.cjs CHANGED
@@ -71823,20 +71823,50 @@ function extractPHPFunctionsFromStack(stack) {
71823
71823
  }
71824
71824
  }
71825
71825
 
71826
+ // packages/php-wasm/util/src/lib/sleep.ts
71827
+ var SleepFinished = Symbol("SleepFinished");
71828
+ function sleep(ms) {
71829
+ return new Promise((resolve) => {
71830
+ setTimeout(() => resolve(SleepFinished), ms);
71831
+ });
71832
+ }
71833
+
71826
71834
  // packages/php-wasm/util/src/lib/semaphore.ts
71835
+ var AcquireTimeoutError = class extends Error {
71836
+ constructor() {
71837
+ super("Acquiring lock timed out");
71838
+ }
71839
+ };
71827
71840
  var Semaphore = class {
71828
- constructor({ concurrency }) {
71841
+ constructor({ concurrency, timeout }) {
71829
71842
  this._running = 0;
71830
71843
  this.concurrency = concurrency;
71844
+ this.timeout = timeout;
71831
71845
  this.queue = [];
71832
71846
  }
71847
+ get remaining() {
71848
+ return this.concurrency - this.running;
71849
+ }
71833
71850
  get running() {
71834
71851
  return this._running;
71835
71852
  }
71836
71853
  async acquire() {
71837
71854
  while (true) {
71838
71855
  if (this._running >= this.concurrency) {
71839
- await new Promise((resolve) => this.queue.push(resolve));
71856
+ const acquired = new Promise((resolve) => {
71857
+ this.queue.push(resolve);
71858
+ });
71859
+ if (this.timeout !== void 0) {
71860
+ await Promise.race([acquired, sleep(this.timeout)]).then(
71861
+ (value) => {
71862
+ if (value === SleepFinished) {
71863
+ throw new AcquireTimeoutError();
71864
+ }
71865
+ }
71866
+ );
71867
+ } else {
71868
+ await acquired;
71869
+ }
71840
71870
  } else {
71841
71871
  this._running++;
71842
71872
  let released = false;
@@ -72135,104 +72165,6 @@ var SupportedPHPVersions = [
72135
72165
  ];
72136
72166
  var LatestSupportedPHPVersion = SupportedPHPVersions[0];
72137
72167
 
72138
- // packages/php-wasm/universal/src/lib/php-browser.ts
72139
- var PHPBrowser = class {
72140
- #cookies;
72141
- #config;
72142
- /**
72143
- * @param server - The PHP server to browse.
72144
- * @param config - The browser configuration.
72145
- */
72146
- constructor(requestHandler, config = {}) {
72147
- this.requestHandler = requestHandler;
72148
- this.#cookies = {};
72149
- this.#config = {
72150
- handleRedirects: false,
72151
- maxRedirects: 4,
72152
- ...config
72153
- };
72154
- }
72155
- /**
72156
- * Sends the request to the server.
72157
- *
72158
- * When cookies are present in the response, this method stores
72159
- * them and sends them with any subsequent requests.
72160
- *
72161
- * When a redirection is present in the response, this method
72162
- * follows it by discarding a response and sending a subsequent
72163
- * request.
72164
- *
72165
- * @param request - The request.
72166
- * @param redirects - Internal. The number of redirects handled so far.
72167
- * @returns PHPRequestHandler response.
72168
- */
72169
- async request(request, redirects = 0) {
72170
- const response = await this.requestHandler.request({
72171
- ...request,
72172
- headers: {
72173
- ...request.headers,
72174
- cookie: this.serializeCookies()
72175
- }
72176
- });
72177
- if (response.headers["set-cookie"]) {
72178
- this.setCookies(response.headers["set-cookie"]);
72179
- }
72180
- if (this.#config.handleRedirects && response.headers["location"] && redirects < this.#config.maxRedirects) {
72181
- const redirectUrl = new URL(
72182
- response.headers["location"][0],
72183
- this.requestHandler.absoluteUrl
72184
- );
72185
- return this.request(
72186
- {
72187
- url: redirectUrl.toString(),
72188
- method: "GET",
72189
- headers: {}
72190
- },
72191
- redirects + 1
72192
- );
72193
- }
72194
- return response;
72195
- }
72196
- /** @inheritDoc */
72197
- pathToInternalUrl(path) {
72198
- return this.requestHandler.pathToInternalUrl(path);
72199
- }
72200
- /** @inheritDoc */
72201
- internalUrlToPath(internalUrl) {
72202
- return this.requestHandler.internalUrlToPath(internalUrl);
72203
- }
72204
- /** @inheritDoc */
72205
- get absoluteUrl() {
72206
- return this.requestHandler.absoluteUrl;
72207
- }
72208
- /** @inheritDoc */
72209
- get documentRoot() {
72210
- return this.requestHandler.documentRoot;
72211
- }
72212
- setCookies(cookies) {
72213
- for (const cookie of cookies) {
72214
- try {
72215
- if (!cookie.includes("=")) {
72216
- continue;
72217
- }
72218
- const equalsIndex = cookie.indexOf("=");
72219
- const name = cookie.substring(0, equalsIndex);
72220
- const value = cookie.substring(equalsIndex + 1).split(";")[0];
72221
- this.#cookies[name] = value;
72222
- } catch (e) {
72223
- console.error(e);
72224
- }
72225
- }
72226
- }
72227
- serializeCookies() {
72228
- const cookiesArray = [];
72229
- for (const name in this.#cookies) {
72230
- cookiesArray.push(`${name}=${this.#cookies[name]}`);
72231
- }
72232
- return cookiesArray.join("; ");
72233
- }
72234
- };
72235
-
72236
72168
  // packages/php-wasm/universal/src/lib/urls.ts
72237
72169
  var DEFAULT_BASE_URL = "http://example.com";
72238
72170
  function toRelativeUrl(url) {
@@ -72305,6 +72237,38 @@ function fileToUint8Array(file) {
72305
72237
  });
72306
72238
  }
72307
72239
 
72240
+ // packages/php-wasm/universal/src/lib/http-cookie-store.ts
72241
+ var HttpCookieStore = class {
72242
+ constructor() {
72243
+ this.cookies = {};
72244
+ }
72245
+ rememberCookiesFromResponseHeaders(headers) {
72246
+ if (!headers?.["set-cookie"]) {
72247
+ return;
72248
+ }
72249
+ for (const setCookie of headers["set-cookie"]) {
72250
+ try {
72251
+ if (!setCookie.includes("=")) {
72252
+ continue;
72253
+ }
72254
+ const equalsIndex = setCookie.indexOf("=");
72255
+ const name = setCookie.substring(0, equalsIndex);
72256
+ const value = setCookie.substring(equalsIndex + 1).split(";")[0];
72257
+ this.cookies[name] = value;
72258
+ } catch (e) {
72259
+ console.error(e);
72260
+ }
72261
+ }
72262
+ }
72263
+ getCookieRequestHeader() {
72264
+ const cookiesArray = [];
72265
+ for (const name in this.cookies) {
72266
+ cookiesArray.push(`${name}=${this.cookies[name]}`);
72267
+ }
72268
+ return cookiesArray.join("; ");
72269
+ }
72270
+ };
72271
+
72308
72272
  // packages/php-wasm/universal/src/lib/php-request-handler.ts
72309
72273
  var PHPRequestHandler = class {
72310
72274
  #DOCROOT;
@@ -72315,6 +72279,7 @@ var PHPRequestHandler = class {
72315
72279
  #PATHNAME;
72316
72280
  #ABSOLUTE_URL;
72317
72281
  #semaphore;
72282
+ #cookieStore;
72318
72283
  /**
72319
72284
  * @param php - The PHP instance.
72320
72285
  * @param config - Request Handler configuration.
@@ -72327,6 +72292,7 @@ var PHPRequestHandler = class {
72327
72292
  rewriteRules = []
72328
72293
  } = config;
72329
72294
  this.php = php;
72295
+ this.#cookieStore = new HttpCookieStore();
72330
72296
  this.#DOCROOT = documentRoot;
72331
72297
  const url = new URL(absoluteUrl);
72332
72298
  this.#HOSTNAME = url.hostname;
@@ -72345,11 +72311,23 @@ var PHPRequestHandler = class {
72345
72311
  ].join("");
72346
72312
  this.rewriteRules = rewriteRules;
72347
72313
  }
72348
- /** @inheritDoc */
72314
+ /**
72315
+ * Converts a path to an absolute URL based at the PHPRequestHandler
72316
+ * root.
72317
+ *
72318
+ * @param path The server path to convert to an absolute URL.
72319
+ * @returns The absolute URL.
72320
+ */
72349
72321
  pathToInternalUrl(path) {
72350
72322
  return `${this.absoluteUrl}${path}`;
72351
72323
  }
72352
- /** @inheritDoc */
72324
+ /**
72325
+ * Converts an absolute URL based at the PHPRequestHandler to a relative path
72326
+ * without the server pathname and scope.
72327
+ *
72328
+ * @param internalUrl An absolute URL based at the PHPRequestHandler root.
72329
+ * @returns The relative path.
72330
+ */
72353
72331
  internalUrlToPath(internalUrl) {
72354
72332
  const url = new URL(internalUrl);
72355
72333
  if (url.pathname.startsWith(this.#PATHNAME)) {
@@ -72360,15 +72338,67 @@ var PHPRequestHandler = class {
72360
72338
  get isRequestRunning() {
72361
72339
  return this.#semaphore.running > 0;
72362
72340
  }
72363
- /** @inheritDoc */
72341
+ /**
72342
+ * The absolute URL of this PHPRequestHandler instance.
72343
+ */
72364
72344
  get absoluteUrl() {
72365
72345
  return this.#ABSOLUTE_URL;
72366
72346
  }
72367
- /** @inheritDoc */
72347
+ /**
72348
+ * The directory in the PHP filesystem where the server will look
72349
+ * for the files to serve. Default: `/var/www`.
72350
+ */
72368
72351
  get documentRoot() {
72369
72352
  return this.#DOCROOT;
72370
72353
  }
72371
- /** @inheritDoc */
72354
+ /**
72355
+ * Serves the request – either by serving a static file, or by
72356
+ * dispatching it to the PHP runtime.
72357
+ *
72358
+ * The request() method mode behaves like a web server and only works if
72359
+ * the PHP was initialized with a `requestHandler` option (which the online version
72360
+ * of WordPress Playground does by default).
72361
+ *
72362
+ * In the request mode, you pass an object containing the request information
72363
+ * (method, headers, body, etc.) and the path to the PHP file to run:
72364
+ *
72365
+ * ```ts
72366
+ * const php = PHP.load('7.4', {
72367
+ * requestHandler: {
72368
+ * documentRoot: "/www"
72369
+ * }
72370
+ * })
72371
+ * php.writeFile("/www/index.php", `<?php echo file_get_contents("php://input");`);
72372
+ * const result = await php.request({
72373
+ * method: "GET",
72374
+ * headers: {
72375
+ * "Content-Type": "text/plain"
72376
+ * },
72377
+ * body: "Hello world!",
72378
+ * path: "/www/index.php"
72379
+ * });
72380
+ * // result.text === "Hello world!"
72381
+ * ```
72382
+ *
72383
+ * The `request()` method cannot be used in conjunction with `cli()`.
72384
+ *
72385
+ * @example
72386
+ * ```js
72387
+ * const output = await php.request({
72388
+ * method: 'GET',
72389
+ * url: '/index.php',
72390
+ * headers: {
72391
+ * 'X-foo': 'bar',
72392
+ * },
72393
+ * body: {
72394
+ * foo: 'bar',
72395
+ * },
72396
+ * });
72397
+ * console.log(output.stdout); // "Hello world!"
72398
+ * ```
72399
+ *
72400
+ * @param request - PHP Request data.
72401
+ */
72372
72402
  async request(request) {
72373
72403
  const isAbsolute = request.url.startsWith("http://") || request.url.startsWith("https://");
72374
72404
  const requestedUrl = new URL(
@@ -72442,16 +72472,11 @@ var PHPRequestHandler = class {
72442
72472
  }
72443
72473
  const release = await this.#semaphore.acquire();
72444
72474
  try {
72445
- this.php.addServerGlobalEntry("REMOTE_ADDR", "127.0.0.1");
72446
- this.php.addServerGlobalEntry("DOCUMENT_ROOT", this.#DOCROOT);
72447
- this.php.addServerGlobalEntry(
72448
- "HTTPS",
72449
- this.#ABSOLUTE_URL.startsWith("https://") ? "on" : ""
72450
- );
72451
72475
  let preferredMethod = "GET";
72452
72476
  const headers = {
72453
72477
  host: this.#HOST,
72454
- ...normalizeHeaders(request.headers || {})
72478
+ ...normalizeHeaders(request.headers || {}),
72479
+ cookie: this.#cookieStore.getCookieRequestHeader()
72455
72480
  };
72456
72481
  let body = request.body;
72457
72482
  if (typeof body === "object" && !(body instanceof Uint8Array)) {
@@ -72473,17 +72498,26 @@ var PHPRequestHandler = class {
72473
72498
  );
72474
72499
  }
72475
72500
  try {
72476
- return await this.php.run({
72501
+ const response = await this.php.run({
72477
72502
  relativeUri: ensurePathPrefix(
72478
72503
  toRelativeUrl(requestedUrl),
72479
72504
  this.#PATHNAME
72480
72505
  ),
72481
72506
  protocol: this.#PROTOCOL,
72482
72507
  method: request.method || preferredMethod,
72508
+ $_SERVER: {
72509
+ REMOTE_ADDR: "127.0.0.1",
72510
+ DOCUMENT_ROOT: this.#DOCROOT,
72511
+ HTTPS: this.#ABSOLUTE_URL.startsWith("https://") ? "on" : ""
72512
+ },
72483
72513
  body,
72484
72514
  scriptPath,
72485
72515
  headers
72486
72516
  });
72517
+ this.#cookieStore.rememberCookiesFromResponseHeaders(
72518
+ response.headers
72519
+ );
72520
+ return response;
72487
72521
  } catch (error) {
72488
72522
  const executionError = error;
72489
72523
  if (executionError?.response) {
@@ -72561,6 +72595,38 @@ function inferMimeType(path) {
72561
72595
  case "txt":
72562
72596
  case "md":
72563
72597
  return "text/plain";
72598
+ case "pdf":
72599
+ return "application/pdf";
72600
+ case "webp":
72601
+ return "image/webp";
72602
+ case "mp3":
72603
+ return "audio/mpeg";
72604
+ case "mp4":
72605
+ return "video/mp4";
72606
+ case "csv":
72607
+ return "text/csv";
72608
+ case "xls":
72609
+ return "application/vnd.ms-excel";
72610
+ case "xlsx":
72611
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
72612
+ case "doc":
72613
+ return "application/msword";
72614
+ case "docx":
72615
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
72616
+ case "ppt":
72617
+ return "application/vnd.ms-powerpoint";
72618
+ case "pptx":
72619
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
72620
+ case "zip":
72621
+ return "application/zip";
72622
+ case "rar":
72623
+ return "application/x-rar-compressed";
72624
+ case "tar":
72625
+ return "application/x-tar";
72626
+ case "gz":
72627
+ return "application/gzip";
72628
+ case "7z":
72629
+ return "application/x-7z-compressed";
72564
72630
  default:
72565
72631
  return "application-octet-stream";
72566
72632
  }
@@ -72775,7 +72841,6 @@ var BasePHP2 = class {
72775
72841
  this.#phpIniOverrides = [];
72776
72842
  this.#webSapiInitialized = false;
72777
72843
  this.#wasmErrorsTarget = null;
72778
- this.#serverEntries = {};
72779
72844
  this.#eventListeners = /* @__PURE__ */ new Map();
72780
72845
  this.#messageListeners = [];
72781
72846
  this.semaphore = new Semaphore({ concurrency: 1 });
@@ -72783,9 +72848,7 @@ var BasePHP2 = class {
72783
72848
  this.initializeRuntime(PHPRuntimeId);
72784
72849
  }
72785
72850
  if (serverOptions) {
72786
- this.requestHandler = new PHPBrowser(
72787
- new PHPRequestHandler(this, serverOptions)
72788
- );
72851
+ this.requestHandler = new PHPRequestHandler(this, serverOptions);
72789
72852
  }
72790
72853
  }
72791
72854
  static {
@@ -72796,7 +72859,6 @@ var BasePHP2 = class {
72796
72859
  #sapiName;
72797
72860
  #webSapiInitialized;
72798
72861
  #wasmErrorsTarget;
72799
- #serverEntries;
72800
72862
  #eventListeners;
72801
72863
  #messageListeners;
72802
72864
  addEventListener(eventType, listener) {
@@ -72830,21 +72892,19 @@ var BasePHP2 = class {
72830
72892
  }
72831
72893
  /** @inheritDoc */
72832
72894
  get absoluteUrl() {
72833
- return this.requestHandler.requestHandler.absoluteUrl;
72895
+ return this.requestHandler.absoluteUrl;
72834
72896
  }
72835
72897
  /** @inheritDoc */
72836
72898
  get documentRoot() {
72837
- return this.requestHandler.requestHandler.documentRoot;
72899
+ return this.requestHandler.documentRoot;
72838
72900
  }
72839
72901
  /** @inheritDoc */
72840
72902
  pathToInternalUrl(path) {
72841
- return this.requestHandler.requestHandler.pathToInternalUrl(path);
72903
+ return this.requestHandler.pathToInternalUrl(path);
72842
72904
  }
72843
72905
  /** @inheritDoc */
72844
72906
  internalUrlToPath(internalUrl) {
72845
- return this.requestHandler.requestHandler.internalUrlToPath(
72846
- internalUrl
72847
- );
72907
+ return this.requestHandler.internalUrlToPath(internalUrl);
72848
72908
  }
72849
72909
  initializeRuntime(runtimeId) {
72850
72910
  if (this[__private__dont__use]) {
@@ -72909,11 +72969,11 @@ var BasePHP2 = class {
72909
72969
  this[__private__dont__use].FS.chdir(path);
72910
72970
  }
72911
72971
  /** @inheritDoc */
72912
- async request(request, maxRedirects) {
72972
+ async request(request) {
72913
72973
  if (!this.requestHandler) {
72914
72974
  throw new Error("No request handler available.");
72915
72975
  }
72916
- return this.requestHandler.request(request, maxRedirects);
72976
+ return this.requestHandler.request(request);
72917
72977
  }
72918
72978
  /** @inheritDoc */
72919
72979
  async run(request) {
@@ -72934,7 +72994,12 @@ var BasePHP2 = class {
72934
72994
  this.#setRequestMethod(request.method || "GET");
72935
72995
  const headers = normalizeHeaders(request.headers || {});
72936
72996
  const host = headers["host"] || "example.com:443";
72937
- this.#setRequestHostAndProtocol(host, request.protocol || "http");
72997
+ const port = this.#inferPortFromHostAndProtocol(
72998
+ host,
72999
+ request.protocol || "http"
73000
+ );
73001
+ this.#setRequestHost(host);
73002
+ this.#setRequestPort(port);
72938
73003
  this.#setRequestHeaders(headers);
72939
73004
  if (request.body) {
72940
73005
  heapBodyPointer = this.#setRequestBody(request.body);
@@ -72942,7 +73007,14 @@ var BasePHP2 = class {
72942
73007
  if (typeof request.code === "string") {
72943
73008
  this.#setPHPCode(" ?>" + request.code);
72944
73009
  }
72945
- this.#addServerGlobalEntriesInWasm();
73010
+ const $_SERVER = this.#prepareServerEntries(
73011
+ request.$_SERVER,
73012
+ headers,
73013
+ port
73014
+ );
73015
+ for (const key in $_SERVER) {
73016
+ this.#setServerGlobalEntry(key, $_SERVER[key]);
73017
+ }
72946
73018
  const env = request.env || {};
72947
73019
  for (const key in env) {
72948
73020
  this.#setEnv(key, env[key]);
@@ -72980,6 +73052,29 @@ var BasePHP2 = class {
72980
73052
  }
72981
73053
  }
72982
73054
  }
73055
+ /**
73056
+ * Prepares the $_SERVER entries for the PHP runtime.
73057
+ *
73058
+ * @param defaults Default entries to include in $_SERVER.
73059
+ * @param headers HTTP headers to include in $_SERVER (as HTTP_ prefixed entries).
73060
+ * @param port HTTP port, used to determine infer $_SERVER['HTTPS'] value if none
73061
+ * was provided.
73062
+ * @returns Computed $_SERVER entries.
73063
+ */
73064
+ #prepareServerEntries(defaults, headers, port) {
73065
+ const $_SERVER = {
73066
+ ...defaults || {}
73067
+ };
73068
+ $_SERVER["HTTPS"] = $_SERVER["HTTPS"] || port === 443 ? "on" : "off";
73069
+ for (const name in headers) {
73070
+ let HTTP_prefix = "HTTP_";
73071
+ if (["content-type", "content-length"].includes(name.toLowerCase())) {
73072
+ HTTP_prefix = "";
73073
+ }
73074
+ $_SERVER[`${HTTP_prefix}${name.toUpperCase().replace(/-/g, "_")}`] = headers[name];
73075
+ }
73076
+ return $_SERVER;
73077
+ }
72983
73078
  #initWebRuntime() {
72984
73079
  this.setPhpIniEntry("auto_prepend_file", "/internal/consts.php");
72985
73080
  if (!this.fileExists("/internal/consts.php")) {
@@ -73050,13 +73145,23 @@ var BasePHP2 = class {
73050
73145
  );
73051
73146
  }
73052
73147
  }
73053
- #setRequestHostAndProtocol(host, protocol) {
73148
+ #setRequestHost(host) {
73054
73149
  this[__private__dont__use].ccall(
73055
73150
  "wasm_set_request_host",
73056
73151
  null,
73057
73152
  [STRING],
73058
73153
  [host]
73059
73154
  );
73155
+ }
73156
+ #setRequestPort(port) {
73157
+ this[__private__dont__use].ccall(
73158
+ "wasm_set_request_port",
73159
+ null,
73160
+ [NUMBER],
73161
+ [port]
73162
+ );
73163
+ }
73164
+ #inferPortFromHostAndProtocol(host, protocol) {
73060
73165
  let port;
73061
73166
  try {
73062
73167
  port = parseInt(new URL(host).port, 10);
@@ -73065,15 +73170,7 @@ var BasePHP2 = class {
73065
73170
  if (!port || isNaN(port) || port === 80) {
73066
73171
  port = protocol === "https" ? 443 : 80;
73067
73172
  }
73068
- this[__private__dont__use].ccall(
73069
- "wasm_set_request_port",
73070
- null,
73071
- [NUMBER],
73072
- [port]
73073
- );
73074
- if (protocol === "https" || !protocol && port === 443) {
73075
- this.addServerGlobalEntry("HTTPS", "on");
73076
- }
73173
+ return port;
73077
73174
  }
73078
73175
  #setRequestMethod(method) {
73079
73176
  this[__private__dont__use].ccall(
@@ -73108,16 +73205,6 @@ var BasePHP2 = class {
73108
73205
  [parseInt(headers["content-length"], 10)]
73109
73206
  );
73110
73207
  }
73111
- for (const name in headers) {
73112
- let HTTP_prefix = "HTTP_";
73113
- if (["content-type", "content-length"].includes(name.toLowerCase())) {
73114
- HTTP_prefix = "";
73115
- }
73116
- this.addServerGlobalEntry(
73117
- `${HTTP_prefix}${name.toUpperCase().replace(/-/g, "_")}`,
73118
- headers[name]
73119
- );
73120
- }
73121
73208
  }
73122
73209
  #setRequestBody(body) {
73123
73210
  let size, contentLength;
@@ -73166,18 +73253,13 @@ var BasePHP2 = class {
73166
73253
  [path]
73167
73254
  );
73168
73255
  }
73169
- addServerGlobalEntry(key, value) {
73170
- this.#serverEntries[key] = value;
73171
- }
73172
- #addServerGlobalEntriesInWasm() {
73173
- for (const key in this.#serverEntries) {
73174
- this[__private__dont__use].ccall(
73175
- "wasm_add_SERVER_entry",
73176
- null,
73177
- [STRING, STRING],
73178
- [key, this.#serverEntries[key]]
73179
- );
73180
- }
73256
+ #setServerGlobalEntry(key, value) {
73257
+ this[__private__dont__use].ccall(
73258
+ "wasm_add_SERVER_entry",
73259
+ null,
73260
+ [STRING, STRING],
73261
+ [key, value]
73262
+ );
73181
73263
  }
73182
73264
  #setEnv(name, value) {
73183
73265
  this[__private__dont__use].ccall(
@@ -73259,7 +73341,6 @@ var BasePHP2 = class {
73259
73341
  throw rethrown;
73260
73342
  } finally {
73261
73343
  this.#wasmErrorsTarget?.removeEventListener("error", errorListener);
73262
- this.#serverEntries = {};
73263
73344
  }
73264
73345
  const { headers, httpStatusCode } = this.#getResponseHeaders();
73265
73346
  return new PHPResponse(
@@ -73357,8 +73438,12 @@ var BasePHP2 = class {
73357
73438
  * interrupting the operations of this PHP instance.
73358
73439
  *
73359
73440
  * @param runtime
73441
+ * @param cwd. Internal, the VFS path to recreate in the new runtime.
73442
+ * This arg is temporary and will be removed once BasePHP
73443
+ * is fully decoupled from the request handler and
73444
+ * accepts a constructor-level cwd argument.
73360
73445
  */
73361
- hotSwapPHPRuntime(runtime) {
73446
+ hotSwapPHPRuntime(runtime, cwd) {
73362
73447
  const oldFS = this[__private__dont__use].FS;
73363
73448
  try {
73364
73449
  this.exit();
@@ -73371,9 +73456,8 @@ var BasePHP2 = class {
73371
73456
  if (this.#sapiName) {
73372
73457
  this.setSapiName(this.#sapiName);
73373
73458
  }
73374
- if (this.requestHandler) {
73375
- const docroot = this.documentRoot;
73376
- copyFS(oldFS, this[__private__dont__use].FS, docroot);
73459
+ if (cwd) {
73460
+ copyFS(oldFS, this[__private__dont__use].FS, cwd);
73377
73461
  }
73378
73462
  }
73379
73463
  exit(code = 0) {
package/index.d.ts CHANGED
@@ -83,137 +83,17 @@ export type PHPEvent = PHPRequestEndEvent | PHPRequestErrorEvent | PHPRuntimeIni
83
83
  * A callback function that handles PHP events.
84
84
  */
85
85
  export type PHPEventListener = (event: PHPEvent) => void;
86
- /**
87
- * Handles HTTP requests using PHP runtime as a backend.
88
- *
89
- * @public
90
- * @example Use PHPRequestHandler implicitly with a new PHP instance:
91
- * ```js
92
- * import { PHP } from '@php-wasm/web';
93
- *
94
- * const php = await PHP.load( '7.4', {
95
- * requestHandler: {
96
- * // PHP FS path to serve the files from:
97
- * documentRoot: '/www',
98
- *
99
- * // Used to populate $_SERVER['SERVER_NAME'] etc.:
100
- * absoluteUrl: 'http://127.0.0.1'
101
- * }
102
- * } );
103
- *
104
- * php.mkdirTree('/www');
105
- * php.writeFile('/www/index.php', '<?php echo "Hi from PHP!"; ');
106
- *
107
- * const response = await php.request({ path: '/index.php' });
108
- * console.log(response.text);
109
- * // "Hi from PHP!"
110
- * ```
111
- *
112
- * @example Explicitly create a PHPRequestHandler instance and run a PHP script:
113
- * ```js
114
- * import {
115
- * loadPHPRuntime,
116
- * PHP,
117
- * PHPRequestHandler,
118
- * getPHPLoaderModule,
119
- * } from '@php-wasm/web';
120
- *
121
- * const runtime = await loadPHPRuntime( await getPHPLoaderModule('7.4') );
122
- * const php = new PHP( runtime );
123
- *
124
- * php.mkdirTree('/www');
125
- * php.writeFile('/www/index.php', '<?php echo "Hi from PHP!"; ');
126
- *
127
- * const server = new PHPRequestHandler(php, {
128
- * // PHP FS path to serve the files from:
129
- * documentRoot: '/www',
130
- *
131
- * // Used to populate $_SERVER['SERVER_NAME'] etc.:
132
- * absoluteUrl: 'http://127.0.0.1'
133
- * });
134
- *
135
- * const response = server.request({ path: '/index.php' });
136
- * console.log(response.text);
137
- * // "Hi from PHP!"
138
- * ```
139
- */
140
- export interface RequestHandler {
141
- /**
142
- * Serves the request – either by serving a static file, or by
143
- * dispatching it to the PHP runtime.
144
- *
145
- * The request() method mode behaves like a web server and only works if
146
- * the PHP was initialized with a `requestHandler` option (which the online version
147
- * of WordPress Playground does by default).
148
- *
149
- * In the request mode, you pass an object containing the request information
150
- * (method, headers, body, etc.) and the path to the PHP file to run:
151
- *
152
- * ```ts
153
- * const php = PHP.load('7.4', {
154
- * requestHandler: {
155
- * documentRoot: "/www"
156
- * }
157
- * })
158
- * php.writeFile("/www/index.php", `<?php echo file_get_contents("php://input");`);
159
- * const result = await php.request({
160
- * method: "GET",
161
- * headers: {
162
- * "Content-Type": "text/plain"
163
- * },
164
- * body: "Hello world!",
165
- * path: "/www/index.php"
166
- * });
167
- * // result.text === "Hello world!"
168
- * ```
169
- *
170
- * The `request()` method cannot be used in conjunction with `cli()`.
171
- *
172
- * @example
173
- * ```js
174
- * const output = await php.request({
175
- * method: 'GET',
176
- * url: '/index.php',
177
- * headers: {
178
- * 'X-foo': 'bar',
179
- * },
180
- * body: {
181
- * foo: 'bar',
182
- * },
183
- * });
184
- * console.log(output.stdout); // "Hello world!"
185
- * ```
186
- *
187
- * @param request - PHP Request data.
188
- */
189
- request(request: PHPRequest, maxRedirects?: number): Promise<PHPResponse>;
190
- /**
191
- * Converts a path to an absolute URL based at the PHPRequestHandler
192
- * root.
193
- *
194
- * @param path The server path to convert to an absolute URL.
195
- * @returns The absolute URL.
196
- */
86
+ export interface IsomorphicLocalPHP {
87
+ /** @deprecated Use PHPRequestHandler instead. */
88
+ request(request: PHPRequest): Promise<PHPResponse>;
89
+ /** @deprecated Use PHPRequestHandler instead. */
197
90
  pathToInternalUrl(path: string): string;
198
- /**
199
- * Converts an absolute URL based at the PHPRequestHandler to a relative path
200
- * without the server pathname and scope.
201
- *
202
- * @param internalUrl An absolute URL based at the PHPRequestHandler root.
203
- * @returns The relative path.
204
- */
91
+ /** @deprecated Use PHPRequestHandler instead. */
205
92
  internalUrlToPath(internalUrl: string): string;
206
- /**
207
- * The absolute URL of this PHPRequestHandler instance.
208
- */
93
+ /** @deprecated Use PHPRequestHandler instead. */
209
94
  absoluteUrl: string;
210
- /**
211
- * The directory in the PHP filesystem where the server will look
212
- * for the files to serve. Default: `/var/www`.
213
- */
95
+ /** @deprecated Use PHPRequestHandler instead. */
214
96
  documentRoot: string;
215
- }
216
- export interface IsomorphicLocalPHP extends RequestHandler {
217
97
  /**
218
98
  * Sets the SAPI name exposed by the PHP module.
219
99
  * @param newName - The new SAPI name.
@@ -449,14 +329,6 @@ export interface IsomorphicLocalPHP extends RequestHandler {
449
329
  * @param listener Callback function to handle the message.
450
330
  */
451
331
  onMessage(listener: MessageListener): void;
452
- /**
453
- * Registers a handler to spawns a child process when
454
- * `proc_open()`, `popen()`, `exec()`, `system()`, or `passthru()`
455
- * is called.
456
- *
457
- * @param handler Callback function to spawn a process.
458
- */
459
- setSpawnHandler(handler: SpawnHandler | string): void;
460
332
  }
461
333
  export type MessageListener = (data: string) => Promise<string | Uint8Array | void> | string | void;
462
334
  export interface EventEmitter {
@@ -519,6 +391,10 @@ export interface PHPRunOptions {
519
391
  * Environment variables to set for this run.
520
392
  */
521
393
  env?: Record<string, string>;
394
+ /**
395
+ * $_SERVER entries to set for this run.
396
+ */
397
+ $_SERVER?: Record<string, string>;
522
398
  /**
523
399
  * The code snippet to eval instead of a php file.
524
400
  */
@@ -569,7 +445,7 @@ export interface PHPRequestHandlerConfiguration {
569
445
  */
570
446
  rewriteRules?: RewriteRule[];
571
447
  }
572
- declare class PHPRequestHandler implements RequestHandler {
448
+ declare class PHPRequestHandler {
573
449
  #private;
574
450
  rewriteRules: RewriteRule[];
575
451
  /**
@@ -581,62 +457,81 @@ declare class PHPRequestHandler implements RequestHandler {
581
457
  * @param config - Request Handler configuration.
582
458
  */
583
459
  constructor(php: BasePHP, config?: PHPRequestHandlerConfiguration);
584
- /** @inheritDoc */
460
+ /**
461
+ * Converts a path to an absolute URL based at the PHPRequestHandler
462
+ * root.
463
+ *
464
+ * @param path The server path to convert to an absolute URL.
465
+ * @returns The absolute URL.
466
+ */
585
467
  pathToInternalUrl(path: string): string;
586
- /** @inheritDoc */
587
- internalUrlToPath(internalUrl: string): string;
588
- get isRequestRunning(): boolean;
589
- /** @inheritDoc */
590
- get absoluteUrl(): string;
591
- /** @inheritDoc */
592
- get documentRoot(): string;
593
- /** @inheritDoc */
594
- request(request: PHPRequest): Promise<PHPResponse>;
595
- }
596
- export interface PHPBrowserConfiguration {
597
468
  /**
598
- * Should handle redirects internally?
469
+ * Converts an absolute URL based at the PHPRequestHandler to a relative path
470
+ * without the server pathname and scope.
471
+ *
472
+ * @param internalUrl An absolute URL based at the PHPRequestHandler root.
473
+ * @returns The relative path.
599
474
  */
600
- handleRedirects?: boolean;
475
+ internalUrlToPath(internalUrl: string): string;
476
+ get isRequestRunning(): boolean;
601
477
  /**
602
- * The maximum number of redirects to follow internally. Once
603
- * exceeded, request() will return the redirecting response.
478
+ * The absolute URL of this PHPRequestHandler instance.
604
479
  */
605
- maxRedirects?: number;
606
- }
607
- declare class PHPBrowser implements RequestHandler {
608
- #private;
609
- requestHandler: PHPRequestHandler;
480
+ get absoluteUrl(): string;
610
481
  /**
611
- * @param server - The PHP server to browse.
612
- * @param config - The browser configuration.
482
+ * The directory in the PHP filesystem where the server will look
483
+ * for the files to serve. Default: `/var/www`.
613
484
  */
614
- constructor(requestHandler: PHPRequestHandler, config?: PHPBrowserConfiguration);
485
+ get documentRoot(): string;
615
486
  /**
616
- * Sends the request to the server.
487
+ * Serves the request either by serving a static file, or by
488
+ * dispatching it to the PHP runtime.
617
489
  *
618
- * When cookies are present in the response, this method stores
619
- * them and sends them with any subsequent requests.
490
+ * The request() method mode behaves like a web server and only works if
491
+ * the PHP was initialized with a `requestHandler` option (which the online version
492
+ * of WordPress Playground does by default).
620
493
  *
621
- * When a redirection is present in the response, this method
622
- * follows it by discarding a response and sending a subsequent
623
- * request.
494
+ * In the request mode, you pass an object containing the request information
495
+ * (method, headers, body, etc.) and the path to the PHP file to run:
624
496
  *
625
- * @param request - The request.
626
- * @param redirects - Internal. The number of redirects handled so far.
627
- * @returns PHPRequestHandler response.
497
+ * ```ts
498
+ * const php = PHP.load('7.4', {
499
+ * requestHandler: {
500
+ * documentRoot: "/www"
501
+ * }
502
+ * })
503
+ * php.writeFile("/www/index.php", `<?php echo file_get_contents("php://input");`);
504
+ * const result = await php.request({
505
+ * method: "GET",
506
+ * headers: {
507
+ * "Content-Type": "text/plain"
508
+ * },
509
+ * body: "Hello world!",
510
+ * path: "/www/index.php"
511
+ * });
512
+ * // result.text === "Hello world!"
513
+ * ```
514
+ *
515
+ * The `request()` method cannot be used in conjunction with `cli()`.
516
+ *
517
+ * @example
518
+ * ```js
519
+ * const output = await php.request({
520
+ * method: 'GET',
521
+ * url: '/index.php',
522
+ * headers: {
523
+ * 'X-foo': 'bar',
524
+ * },
525
+ * body: {
526
+ * foo: 'bar',
527
+ * },
528
+ * });
529
+ * console.log(output.stdout); // "Hello world!"
530
+ * ```
531
+ *
532
+ * @param request - PHP Request data.
628
533
  */
629
- request(request: PHPRequest, redirects?: number): Promise<PHPResponse>;
630
- /** @inheritDoc */
631
- pathToInternalUrl(path: string): string;
632
- /** @inheritDoc */
633
- internalUrlToPath(internalUrl: string): string;
634
- /** @inheritDoc */
635
- get absoluteUrl(): string;
636
- /** @inheritDoc */
637
- get documentRoot(): string;
638
- setCookies(cookies: string[]): void;
639
- serializeCookies(): string;
534
+ request(request: PHPRequest): Promise<PHPResponse>;
640
535
  }
641
536
  export type PHPRuntimeId = number;
642
537
  export type PHPRuntime = any;
@@ -660,16 +555,26 @@ export type EmscriptenOptions = {
660
555
  onRuntimeInitialized?: () => void;
661
556
  monitorRunDependencies?: (left: number) => void;
662
557
  onMessage?: (listener: EmscriptenMessageListener) => void;
558
+ instantiateWasm?: (info: WebAssembly.Imports, receiveInstance: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => void;
663
559
  } & Record<string, any>;
664
560
  export type EmscriptenMessageListener = (type: string, data: string) => void;
665
561
  export interface SemaphoreOptions {
562
+ /**
563
+ * The maximum number of concurrent locks.
564
+ */
666
565
  concurrency: number;
566
+ /**
567
+ * The maximum time to wait for a lock to become available.
568
+ */
569
+ timeout?: number;
667
570
  }
668
571
  declare class Semaphore {
669
572
  private _running;
670
573
  private concurrency;
574
+ private timeout?;
671
575
  private queue;
672
- constructor({ concurrency }: SemaphoreOptions);
576
+ constructor({ concurrency, timeout }: SemaphoreOptions);
577
+ get remaining(): number;
673
578
  get running(): number;
674
579
  acquire(): Promise<() => void>;
675
580
  run<T>(fn: () => T | Promise<T>): Promise<T>;
@@ -678,7 +583,7 @@ declare const __private__dont__use: unique symbol;
678
583
  declare abstract class BasePHP implements IsomorphicLocalPHP {
679
584
  #private;
680
585
  protected [__private__dont__use]: any;
681
- requestHandler?: PHPBrowser;
586
+ requestHandler?: PHPRequestHandler;
682
587
  /**
683
588
  * An exclusive lock that prevent multiple requests from running at
684
589
  * the same time.
@@ -717,10 +622,9 @@ declare abstract class BasePHP implements IsomorphicLocalPHP {
717
622
  /** @inheritDoc */
718
623
  chdir(path: string): void;
719
624
  /** @inheritDoc */
720
- request(request: PHPRequest, maxRedirects?: number): Promise<PHPResponse>;
625
+ request(request: PHPRequest): Promise<PHPResponse>;
721
626
  /** @inheritDoc */
722
627
  run(request: PHPRunOptions): Promise<PHPResponse>;
723
- addServerGlobalEntry(key: string, value: string): void;
724
628
  defineConstant(key: string, value: string | boolean | number | null): void;
725
629
  /** @inheritDoc */
726
630
  mkdir(path: string): void;
@@ -749,8 +653,12 @@ declare abstract class BasePHP implements IsomorphicLocalPHP {
749
653
  * interrupting the operations of this PHP instance.
750
654
  *
751
655
  * @param runtime
656
+ * @param cwd. Internal, the VFS path to recreate in the new runtime.
657
+ * This arg is temporary and will be removed once BasePHP
658
+ * is fully decoupled from the request handler and
659
+ * accepts a constructor-level cwd argument.
752
660
  */
753
- hotSwapPHPRuntime(runtime: number): void;
661
+ hotSwapPHPRuntime(runtime: number, cwd?: string): void;
754
662
  exit(code?: number): void;
755
663
  }
756
664
  export declare function getPHPLoaderModule(version?: SupportedPHPVersion): Promise<PHPLoaderModule>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@php-wasm/node",
3
- "version": "0.6.16",
3
+ "version": "0.7.0",
4
4
  "description": "PHP.wasm for Node.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "license": "GPL-2.0-or-later",
29
29
  "main": "index.cjs",
30
30
  "types": "index.d.ts",
31
- "gitHead": "1981567e7eacecbc4a18c870267c20bf489afd8f",
31
+ "gitHead": "c5eba3d709f2821c4303521e8c81b962e3bcca23",
32
32
  "engines": {
33
33
  "node": ">=18.18.0",
34
34
  "npm": ">=8.11.0"
@@ -37,8 +37,8 @@
37
37
  "comlink": "^4.4.1",
38
38
  "ws": "8.13.0",
39
39
  "yargs": "17.7.2",
40
- "@php-wasm/node-polyfills": "0.6.16",
41
- "@php-wasm/universal": "0.6.16",
42
- "@php-wasm/util": "0.6.16"
40
+ "@php-wasm/node-polyfills": "0.7.0",
41
+ "@php-wasm/universal": "0.7.0",
42
+ "@php-wasm/util": "0.7.0"
43
43
  }
44
44
  }